国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【數(shù)據(jù)結(jié)構(gòu)與算法C++實(shí)現(xiàn)】3、排序算法

這篇具有很好參考價(jià)值的文章主要介紹了【數(shù)據(jù)結(jié)構(gòu)與算法C++實(shí)現(xiàn)】3、排序算法。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

原視頻為左程云的B站教學(xué)



【數(shù)據(jù)結(jié)構(gòu)與算法C++實(shí)現(xiàn)】3、排序算法

1 冒泡排序 O ( N 2 ) O(N^2) O(N2)(與數(shù)據(jù)狀況無關(guān))

外層循環(huán):n個(gè)數(shù)需要冒n-1個(gè)泡上去,剩下的一個(gè)必然是最小的。所以外層循環(huán)執(zhí)行n-1輪
內(nèi)層循環(huán):比大小,第1個(gè)泡需要比n-1次,第2個(gè)泡,比較n-2次…
【數(shù)據(jù)結(jié)構(gòu)與算法C++實(shí)現(xiàn)】3、排序算法

#incldue <vector>
#include <utility>
void bubbleSort(std::vector<int>& arr) 
{
    for (int i = 0; i < arr.size() - 1; i++) {
        for (int j = 0; j < arr.size() - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        // 相鄰元素兩兩對(duì)比
				std::swap(arr[j], arr[j+1]);
            }
        }
    }
}

2 選擇排序 O ( N 2 ) O(N^2) O(N2)(與數(shù)據(jù)狀況無關(guān))

選擇: 每次從待排序序列中選擇最小的一個(gè)放在已排序序列的后一個(gè)位置
【數(shù)據(jù)結(jié)構(gòu)與算法C++實(shí)現(xiàn)】3、排序算法

#include <vector>
#include <utility>
void selectionSort(std::vector<int>& arr){
	int len = arr.size();
	if (arr.empty() || len < 2) return;

	for (int i = 0; i < len -1; i++) { 		// len -1 位置不用再選了,沒得選
		int minIndex = i;		
		for (int j = i + 1; j < len; j++) {	// 在[i+1,len-1]上尋找最小值的下標(biāo)
			minIndex = arr[j] < arr[minIndex] ? j : minIndex;
		}
		std::swap(arr[minIndex], arr[i]);
	}
}

3 插入排序 O ( N 2 ) O(N^2) O(N2)(與數(shù)據(jù)狀況相關(guān))

原理類似于對(duì)撲克牌手牌進(jìn)行排序的過程。將未排序的元素逐個(gè)插入到已排序部分的合適位置。

邏輯順序?yàn)橄茸龅?~0有序, 然后0~1有序, 0~2 … 0~n-1有序

算法流程為:

  • 從第一個(gè)元素開始,該元素可以認(rèn)為已經(jīng)被排序。
  • 從未排序部分取第一個(gè)值,插入到已排序部分的適當(dāng)位置。這個(gè)位置的選取方式為:把當(dāng)前未排序值與左鄰值對(duì)比,如果更小則交換位置。直到遇到邊界或不比左鄰值小則結(jié)束。(內(nèi)循環(huán):找適當(dāng)位置插入)
  • 重復(fù)步驟2,直到所有元素都被插入到已排序序列中。(外循環(huán):遍歷每個(gè)未排序的數(shù))

這個(gè)不像冒泡排序和選擇排序是固定操作(數(shù)據(jù)狀況無關(guān))。插入排序中,如果給的就是有序的,那就外層每層循環(huán)做一次比較就完成了,所以會(huì)是O(N), 但我們說時(shí)間復(fù)雜度都是 worst case 所以還是 O ( N 2 ) O(N^2) O(N2)

【數(shù)據(jù)結(jié)構(gòu)與算法C++實(shí)現(xiàn)】3、排序算法

#include <vector>
#include <utility>
void insertionSort(std::vector<int>& arr)
{
	int len = arr.size();
	if (arr.empty() || len < 2) return;
	// 0~0已經(jīng)有序了
	for (int i = 1; i < len; i++){ 	// 0~i做到有序
		// 把當(dāng)前值(j+1)對(duì)比左鄰值(j),比他小則交換,不滿足循環(huán)條件則說明找到合適位置了
		for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--){ 
			std::swap(arr[j+1], arr[j]);
		}
	}
}

在內(nèi)層循環(huán)中,我們將當(dāng)前元素 arr[j + 1] 與其左鄰元素 arr[j] 進(jìn)行比較,更小則換位,直到找到合適的插入位置。
循環(huán)結(jié)束(找到合適的位置)條件為:達(dá)到左邊界 or 當(dāng)前值比左鄰值大

繼續(xù)往下看建議先掌握 二分查找和基礎(chǔ)遞歸

4 歸并排序 O ( N l o g N ) O(NlogN) O(NlogN)(數(shù)據(jù)狀況無關(guān))

它的核心思想是將待排序的序列不斷劃分成更小的子序列,直到每個(gè)子序列只有一個(gè)元素,然后再將這些子序列兩兩合并,直到最終整個(gè)序列有序。

下面是歸并排序的一般步驟:

  • 分割:將待排序的序列從中間位置分割成兩個(gè)子序列,不斷遞歸地將每個(gè)子序列繼續(xù)分割,直到每個(gè)子序列只剩下一個(gè)元素。
  • 合并:將兩個(gè)有序的子序列合并成一個(gè)有序序列。從兩個(gè)子序列的第一個(gè)元素開始比較,將較小的元素放入臨時(shí)數(shù)組中,并將對(duì)應(yīng)子序列的索引向后移動(dòng)一位,直到其中一個(gè)子序列的元素全部放入臨時(shí)數(shù)組中。然后將另一個(gè)子序列的剩余元素直接放入臨時(shí)數(shù)組中。
  • 重復(fù)合并:重復(fù)進(jìn)行合并操作,直到所有子序列都合并為一個(gè)有序序列。

始終都是 O(NlogN) 的時(shí)間復(fù)雜度,與數(shù)據(jù)無關(guān)。雖然相對(duì)前面的冒泡、選擇、插入排序更快,但是空間復(fù)雜度為O(N)

【數(shù)據(jù)結(jié)構(gòu)與算法C++實(shí)現(xiàn)】3、排序算法

#include <vector>
#include <utility>
// 第二階段:merge階段時(shí),L~M 和 M~R 之間的數(shù)必然有序
void merge(std::vector<int>& arr, int L, int M, int R)
{
	std::vector help(R - L + 1);
	int i = 0;		// 臨時(shí)數(shù)組的起始索引
	int p1 = L;		// 左半部分的起始索引
	int p2 = M + 1;	// 右半部分的起始索引

	//外排序,把兩個(gè)子數(shù)組中的元素從小到大放到help數(shù)組
	while (p1 <= M && p2 <= R){ 
		help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
	}

 	// 如果左邊部分還有剩的,全部依次加到help的末尾
	while (p1 <= M){
		help[i++] = arr[p1++];
	}
	// 同理如果右邊還有剩的
	while (p2 <= R){ 
		help[i++] = arr[p2++];
	}

	// 結(jié)束,把臨時(shí)數(shù)組的內(nèi)容覆蓋到原數(shù)組中
	for (i = 0; i < R - L + 1; i++){
		arr[L + i] = help[i]; // 注意arr從L位置開始寫入的
	}
}

// 第一階段拆分
void mergeSort(std::vector<int>& arr, int L, int R)
{
	if (L == R) return;	// 拆分完畢,不能再拆了
	
	int mid = L + ((R - L) >> 1);
	mergeSort(arr, L, mid);
	mergeSort(arr, mid+1, R);
	merge(arr, L, mid, R);  
}

時(shí)間復(fù)雜度(參考2.1 master公式)

  • T ( N ) = 2 ? T ( N 2 ) + O ( N ) T(N) = 2·T(\frac{N}{2})+O(N) T(N)=2?T(2N?)+O(N)
  • a = 2; b = 2; d = 1
  • l o g a b = 1 = d log_ab=1=d loga?b=1=d,所以時(shí)間復(fù)雜度為 O ( N ? l o g N ) O(N·logN) O(N?logN)

空間復(fù)雜度: O ( N ) O(N) O(N) 。因?yàn)槊看蝝erge的時(shí)候開辟一塊空間,大小為N

4.1 擴(kuò)展:求數(shù)組小和

題目:在一個(gè)數(shù)組中,每一個(gè)數(shù)左邊比當(dāng)前數(shù)小的數(shù)累加起來,叫做這個(gè)數(shù)組的小和。求一個(gè)數(shù)組的小和。
例子:[1,3,4,2,5]
1左邊比1小的數(shù),無
3左邊比3小的數(shù),1
4左邊比4小的數(shù),1、3
2左邊比2小的數(shù),1
5左邊比5小的數(shù),1、3、4、2
所以小和為1+1+3+1+1+3+4+2=16

要求時(shí)間復(fù)雜度O(NlogN),空間復(fù)雜度O(N)

  • 如果每個(gè)位置的元素都遍歷一遍左邊所有元素,找出比它小的數(shù),求和,很簡單就能得到結(jié)果,時(shí)間復(fù)雜度為 O ( N 2 ) O(N^2) O(N2)
  • 轉(zhuǎn)變思維:不是找左邊有多少比當(dāng)前位置的數(shù)更小的數(shù),然后求和,而是統(tǒng)計(jì)右邊有多少個(gè)比當(dāng)前位置的數(shù)更大的數(shù),當(dāng)前數(shù)就該累加多少次,而這個(gè)過程在歸并排序中的外排序中就可以做到。
  • 歸并排序過程中,從下往上看,每次合并兩個(gè)有序數(shù)組時(shí),就能知道每個(gè)數(shù)的當(dāng)前比他大的數(shù)有多少個(gè),然后累加,歸并排序結(jié)束,則小和也計(jì)算結(jié)束

具體講解視頻精準(zhǔn)空降,比看文字可好多了

#include <iostream>
#include <vector>

// 左右兩組合并
long long merge(std::vector<int>& arr, int left, int mid, int right) 
{
    std::vector<int> temp(right - left + 1); // 臨時(shí)數(shù)組用于存儲(chǔ)合并后的結(jié)果
    int i = 0; // 臨時(shí)數(shù)組的起始索引
    int p1 = left; // 左半部分的起始索引
    int p2 = mid + 1; // 右半部分的起始索引
    long long count = 0; // 記錄小和的累加和

    while (p1 <= mid && p2 <= right) {
    	// 只有在左組比右組小時(shí)產(chǎn)生小和
    	count += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0;
		
		help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
    }

    while (p1 <= mid) temp[i++] = arr[p1++];
    while (p2 <= right) temp[i++] = arr[p2++];

    // 將臨時(shí)數(shù)組的結(jié)果復(fù)制回原數(shù)組
    for (i = 0; i < R - L + 1; i++) {
        arr[left + i] = temp[i];
    }

    return count;
}

long long process(std::vector<int>& arr, int left, int right) 
{
    if (left >= right) {
        return 0;
    }
    int mid = left + ((right - left) >> 1);
    long long leftCount = process(arr, left, mid); // 左半部分的小和
    long long rightCount = process(arr, mid + 1, right); // 右半部分的小和
    long long mergeCount = merge(arr, left, mid, right); // 左右側(cè)merge過程中產(chǎn)生的小和

    return leftCount + rightCount + mergeCount; // 總的小和
}

long long calculateSmallSum(std::vector<int>& arr) 
{
    if (arr == nullptr || arr.size() < 2) 
    	return 0;

    return process(arr, 0, arr.size() - 1);
}
int main() 
{
    std::vector<int> arr = {3, 1, 4, 2, 5};
    long long smallSum = calculateSmallSum(arr);
    std::cout << "Small Sum: " << smallSum << std::endl;
    return 0;
}

4.2 擴(kuò)展:求數(shù)組中逆序?qū)Φ臄?shù)量

一個(gè)數(shù)組為3,2,4,5,0,則逆序?qū)?code>{(3,2), (3,0), (2,0), (4,0), (5,0)}5對(duì)。

這個(gè)題與4.1完全等效。4.1求的是右邊有多少個(gè)數(shù)比當(dāng)前元素大,而這里需要求的是右邊
有多少個(gè)數(shù)比當(dāng)前數(shù)更小。右邊有多少個(gè)小的數(shù),就能跟當(dāng)前數(shù)組成多少個(gè)逆序?qū)Α?/p>

5 快速排序 O ( N l o g N ) O(NlogN) O(NlogN)

3.0版本快排,隨機(jī)選擇中樞值,并放到數(shù)組末尾。后面的流程與正??炫畔嗤?。比較詳細(xì)的文章請(qǐng)參考這里

#include <iostream>
#include <vector>
#include <random> // 包含隨機(jī)數(shù)生成器的頭文件
#include <utility>

// 隨機(jī)選擇中樞元素
int selectPivot(std::vector<int>& arr, int low, int high) 
{
    // 創(chuàng)建一個(gè)隨機(jī)數(shù)引擎
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(low, high); // 創(chuàng)建一個(gè)分布,指定范圍
    
    int randomIndex = dis(gen); // 生成一個(gè)隨機(jī)索引
    std::swap(arr[randomIndex], arr[high]); // 將中樞元素放在數(shù)組末尾
    
    return arr[high];
}

// 快速排序函數(shù)
void quickSort(std::vector<int>& arr, int low, int high) 
{
    if (low < high) {
        // 選取樞軸元素----如果是基礎(chǔ)快排,這里直接選擇第1個(gè)或最后一個(gè)作為中樞即可
        int pivot = selectPivot(arr, low, high);
        
        // 將小于樞軸的元素放在數(shù)組的前面,大于樞軸的元素放在數(shù)組后面
        int i = low - 1; // i充當(dāng)一個(gè)指針,指向數(shù)組的前面部分
        for (int j = low; j < high; ++j) {
            if (arr[j] < pivot) {
                ++i;
                std::swap(arr[i], arr[j]);
            }
        }
        // 最后才將樞軸元素放在正確的位置,i+1左邊的數(shù)都比i位置小,i+1右邊的都比它大
        std::swap(arr[i + 1], arr[high]); 
        
        // 遞歸地對(duì)樞軸左側(cè)和右側(cè)的子數(shù)組進(jìn)行排序
        quickSort(arr, low, i);
        quickSort(arr, i + 2, high);
    }
}

6 堆排序 O ( N l o g N ) O(NlogN) O(NlogN)

更詳細(xì)的講解,請(qǐng)參考此文章

堆結(jié)構(gòu)本身比堆排序的意義更加重大

堆的性質(zhì):

  • 是一棵完全二叉樹
  • 每個(gè)節(jié)點(diǎn)的值都大于或等于其子節(jié)點(diǎn)的值,為大根堆;反之為小根堆。構(gòu)建大根堆的時(shí)間復(fù)雜度為O(N)
  • 一般用數(shù)組來表示。數(shù)組下標(biāo)與堆的邏輯對(duì)應(yīng)關(guān)系
    • 位置 i i i 的左孩子節(jié)點(diǎn): 2 i + 1 2i+1 2i+1
    • 位置 i i i 的右孩子節(jié)點(diǎn): 2 i + 2 2i+2 2i+2
    • 位置 i i i 的父節(jié)點(diǎn): i ? 1 2 \Large\frac{i-1}{2} 2i?1?
  • 優(yōu)先級(jí)隊(duì)列不是隊(duì)列,是堆

大根堆(可不看)

  • 只能保證每個(gè)節(jié)點(diǎn)是以該節(jié)點(diǎn)為根的二叉樹的最大值,其下所有節(jié)點(diǎn)并沒有任何順序可言(左子樹和右子樹的數(shù)沒啥關(guān)系)。
  • 主要應(yīng)用是快速獲取一堆數(shù)中的最大值。因?yàn)榇蟾汛_保根節(jié)點(diǎn)是整個(gè)堆中的最大值,所以可以在常數(shù)時(shí)間內(nèi)(O(1))獲取堆中的最大元素。應(yīng)用包括但不限于:
    • 優(yōu)先隊(duì)列:在優(yōu)先隊(duì)列中,每個(gè)元素都有一個(gè)相關(guān)的優(yōu)先級(jí),而大根堆允許高優(yōu)先級(jí)的元素優(yōu)先出隊(duì)。所以優(yōu)先級(jí)隊(duì)列不是隊(duì)列,是堆
    • 堆排序:堆排序是一種基于大根堆的排序算法。它使用大根堆來不斷取出最大值并將其放入已排序的部分,從而實(shí)現(xiàn)排序。
    • Top K 問題:尋找一堆數(shù)中的前 K 個(gè)最大值或最小值是一個(gè)常見的問題,大根堆可以幫助高效解決這類問題。
    • 滑動(dòng)窗口問題:需要在一個(gè)移動(dòng)的窗口內(nèi)快速找到最大值或最小值,大根堆可以幫助高效地維護(hù)窗口內(nèi)的元素,并快速獲取最大值或最小值。

堆結(jié)構(gòu)的兩個(gè)操作

  • heapInsert: 在已有的大根堆中插入一個(gè)新的元素,從下往上冒。注意新的元素在大根堆范圍的緊后一個(gè)位置索引(比如大根堆是0~9號(hào)索引,則待插入元素應(yīng)該放置在10號(hào)索引位置)
    void heapInsert(std::vector<int>& arr, int index)
    {
    	while (arr[index] > arr[(index - 1) / 2]{
    		swap(arr[index], arr[(index-1) / 2]);
    		index = (index - 1) / 2;
    	}
    }
    
  • heapify: 將一個(gè)以 index 為根節(jié)點(diǎn)的子樹或子堆調(diào)整為最大堆,通過不斷調(diào)用這個(gè)函數(shù),可以將整個(gè)數(shù)組轉(zhuǎn)換為最大堆
    void heapify(std::vector<int>& arr, int index, int heapSize) 
    {
        int largest = index; 
        int left = 2 * index + 1; // 左孩子下標(biāo)
        
        while (left < heapSize){
    		// 兩個(gè)孩子中較大的,下標(biāo)給largest,注意判斷右孩子不能越界,=heapSize是越界
        	largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
    		// 父和孩子比較
    		largest = arr[largest] > arr[index] ? largest : index;
    		if (largest == index)
    			break;
    		swap(arr[largest],arr[index]);
    		index = largest;
    		left = index * 2 + 1;
    }
    

    這段代碼復(fù)雜度為 l o g N logN logN(完全二叉樹層數(shù): c e i l ( l o g 2 ( N ) + 1 ) ceil(log2(N) + 1) ceil(log2(N)+1)


堆排序步驟

  1. 先把數(shù)組構(gòu)建為大根堆

    • 思路1:從0到N-1由前到后逐步構(gòu)建大根堆,相當(dāng)于每次在已有大根堆范圍新增一個(gè)數(shù),調(diào)用heapInsert()從下晚上找合適的位置插入,向后擴(kuò)大堆的范圍。構(gòu)建完畢后,heapSize應(yīng)該等于數(shù)組長度
    • 思路2(更快一點(diǎn)):循環(huán)每次都將堆的范圍逐步向前擴(kuò)展,同時(shí)將當(dāng)前循環(huán)迭代中選擇的數(shù)組元素作為新的根節(jié)點(diǎn),然后調(diào)用 heapify()進(jìn)行堆的調(diào)整。視頻地址
  2. 然后,把數(shù)組0位置(根節(jié)點(diǎn))的數(shù)與數(shù)組末尾的數(shù)做交換,這里就提取到了第一個(gè)最大值。然后將堆的范圍縮小1,即heapSize–。heapSize用于表示堆的范圍,必須用一個(gè)變量存儲(chǔ)

  3. 對(duì)根節(jié)點(diǎn)執(zhí)行heapify(),重新構(gòu)建大根堆

  4. 重復(fù)2、3步驟即可升序排好!

void heapInsert(std::vector<int>& arr, int index); // 如果用法2則不需要這個(gè)函數(shù)
void heapify(std::vector<int>& arr, int index, int heapSize);

// 堆排序函數(shù)
void heapSort(std::vector<int>& arr) 
{
	if (arr.size() < 2)
		return;
    // 1.構(gòu)建大根堆(法1),每次把堆邊界后一個(gè)位置的數(shù)插入堆中(從下往上冒)
	for (int i = 0; i < arr.size(); i++){ // O(N)
		heapInsert(arr, i);	// O(logN)
	}
	// 1.構(gòu)建大根堆(法2),從后往前,新元素作為根節(jié)點(diǎn),調(diào)用heapify從上往下找位置放
	for (int i = arr.size() - 1; i >= 0; i--){
		heapify(arr, i, arr.size());
	}
	
	// 2.提出根節(jié)點(diǎn)最大值(與數(shù)組最后一個(gè)元素交換),并縮小堆的邊界
	int heapSize = arr.size(); // 這個(gè)變量必須有,作為堆的邊界
	std::swap(arr[0], arr[--heapSize]; 
	
	// 3.重新構(gòu)建大根堆,除了新來的根節(jié)點(diǎn),下面的子樹都還是滿足大頂堆的
	while(heapSize > 0){ // O(N)
		heapify(arr, 0, heapSize); 
		std::swap(arr[0], arr[--heapSize]);
	}
}

6.1 擴(kuò)展:小范圍排序 N l o g K NlogK NlogK

題目: 已知一個(gè)幾乎有序的數(shù)組,幾乎有序是指,如果把數(shù)組排好順序的話,每個(gè)元素移動(dòng)的距離可以不超過k,并且k相對(duì)于數(shù)組來說比較小。請(qǐng)選擇一個(gè)合適的排序算法針對(duì)這個(gè)數(shù)據(jù)進(jìn)行排序。

解題思路:

  • 整個(gè)數(shù)組的最小值肯定在(0,k-1)范圍,在該范圍構(gòu)建小根堆。把堆頂(最小值)彈出放在數(shù)組位置0上,跟堆排序類似,把緊后位置k上的數(shù)放到小根堆的堆頂
  • 調(diào)整位置,重新構(gòu)建小根堆。然后第二小的數(shù)會(huì)出現(xiàn)在堆頂,再把堆頂放在數(shù)組的位置1 上
  • 反復(fù)操作,如此彈出堆頂?shù)捻樞蚓褪菙?shù)組排好序的順序。
  • 建堆階段:O(K)
  • 插入元素階段:從下標(biāo)為k的元素開始,將它們插入到小根堆中。O(logK),因?yàn)樾「训母叨仁?logK。由于需要插入 N-K 個(gè)元素,所以這個(gè)階段的時(shí)間復(fù)雜度是 O((N-K)logK)
  • 因?yàn)镵相對(duì)N來說足夠小,因此忽略不計(jì),直接看做 N l o g K NlogK NlogK
#include <vector>
#include <queue>	 
#include <algorithm> //std::min
void sortedArrDistanceLessK(std::vector<int> arr, int k)
{
	// 默認(rèn)小根堆,即參數(shù)3默認(rèn)為std::less<T>
	std::priority_queue<int, std::vector<int>, std::less<int>> minHeap;
	// 構(gòu)建 O(K)
	int index = 0;
	for (; index < std::min(arr.size(), k); index++){
		minHeap.push(arr[index]);
	}

	int i = 0;
	for (; index < arr.size(); i++; index++){
		minHeap.push(arr[index]);// 把數(shù)組中,堆范圍后的一個(gè)元素壓入,內(nèi)部會(huì)自動(dòng)找位置插入
		arr[i] = minHeap.top(); // 拿到堆頂元素
		minHeap.pop();   		// 彈出堆頂元素后,會(huì)自動(dòng)重新構(gòu)建大根堆
	}
	

	// 彈出剩余的元素
	while (!minHeap.empty()){
		arr[i++] = minHeap.top();
		minHeap.pop(); // 注意這里pop操作之后會(huì)自動(dòng)調(diào)整數(shù)據(jù)布局,以滿足大根堆性質(zhì) 		
	}
}

7 基數(shù)排序 O ( N ) O(N) O(N)

前面介紹的都是基于比較的排序,而這里則是不基于比較的。

計(jì)數(shù)排序(很少用):對(duì)一定范圍內(nèi)的整數(shù)進(jìn)行排序。它的基本思想是通過統(tǒng)計(jì)每個(gè)元素出現(xiàn)的次數(shù)(需要輔助數(shù)組,長度與待排序數(shù)組的數(shù)值域范圍相同),然后根據(jù)計(jì)數(shù)結(jié)果將元素按順序放回原數(shù)組,從而實(shí)現(xiàn)排序。不適用于范圍很大甚至沒有范圍的排序,因?yàn)樾枰妮o助數(shù)組太大了。

基數(shù)排序: 必須有進(jìn)制,比計(jì)數(shù)排序好,不需要那么多輔助空間。具體講解視頻跳轉(zhuǎn)鏈接,文字說明在代碼部分的后面

#include <vector>
#include <algorithm>
#include <cmath> // pow()
// 最大值的位數(shù)
int maxbits(std::vector<int> arr)
{
	int max = *std::max_element(arr.begin(), arr.end());
	int bits = 0;
	while (max != 0){
		max /= 10; // 取個(gè)位數(shù)
		bits++;
	}
	return bits;
}
// 獲取數(shù)字 num 的第 bit 位數(shù)字
int getDigit(int num, int bit) {
	// 比如1423 n
    return (num / static_cast<int>(std::pow(10, bit - 1))) % 10;
}

void radixSort(std::vector<int> arr, int L, int R, int maxBits)
{
	cosnt int base = 10; // 基數(shù)為10,即按十進(jìn)制進(jìn)行排序
	int i = 0, j = 0;
	// 桶的長度和原數(shù)組長度相同
	int n = arr.size();
	std::vector<int> tmpArr(n);	
	
	// 這個(gè)bit是當(dāng)前十進(jìn)制位
	for (int bit = 1; bit <= maxBits; bit++){// 有多少位就進(jìn)出多少次
		std::vector<int> count(base, 0);
        // 統(tǒng)計(jì)當(dāng)前bit位上為0~9的的數(shù)分別有多少個(gè)
        for (i = 0; i <= R; i++) {
            j = getDigit(num, bit);	
            count[j]++;
        }
        // 將 count 數(shù)組變?yōu)榇娣琶總€(gè)數(shù)字應(yīng)該放置的位置索引(看說明1)
        // 下標(biāo)0~9分別表示<=當(dāng)前下標(biāo)數(shù)的元素個(gè)數(shù)
		// 比如count[5],表示數(shù)組中所有數(shù),在當(dāng)前bit位上的數(shù)<=5的個(gè)數(shù)
        for (i = 1; i < base; i++) {
            count[i] += count[i - 1];
        }
        // 原數(shù)組的數(shù)按當(dāng)前位進(jìn)行一次排序,放入臨時(shí)數(shù)組,從右往左遍歷原數(shù)組(看說明2)
        for (int i = n - 1; i >= 0; i--) {
            j = getDigit(arr[i], bit);
            tmpArr[count[j] - 1] = arr[i];
            count[j]--;
        }
        // 將排好序的臨時(shí)數(shù)組tmpArr拷貝回原數(shù)組 arr
        //std::copy(tmpArr.begin(), tmpArr.end(), arr.begin() + L);
        for (i = L, j = 0; i <= R; i++, j++){
        	arr[i] = tmpArr[j];
        }
	}
}
// 僅用于非負(fù)數(shù)排序
void radixSort(std::vector<int> arr)
{
	if (arr.size() < 2)
		return;
	radixSort(arr, 0, arr.size() - 1, maxbits(arr)); // 重載版本的sort
}

說明1: 計(jì)算每個(gè)數(shù)字在排序后應(yīng)該放在哪個(gè)位置,說白了就是對(duì)原數(shù)組針對(duì)當(dāng)前bit位做一次排序,里每個(gè)元素代表了該數(shù)字在排序后的位置的右邊界索引(排名)。

說明2: 通常基數(shù)排序算法是自右向左的,學(xué)術(shù)上稱之為“最低位優(yōu)先(Least significant digital,LSD)”,能保證基數(shù)排序是穩(wěn)定的,高位優(yōu)先也能保證穩(wěn)定,但寫起來非常麻煩。

舉例說明(結(jié)合代碼看): 當(dāng)對(duì)數(shù)組 { 170, 045, 075, 090, 802,0 24, 002, 066 } 進(jìn)行基數(shù)排序時(shí),數(shù)組中最大的數(shù)字是 802,它有3位。

  • 初始狀態(tài)下,count 數(shù)組初始化為全零:count: { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
  • 第一輪排序(個(gè)位):
    • 統(tǒng)計(jì)個(gè)位數(shù)字出現(xiàn)的次數(shù):count: { 2, 2, 0, 1, 2, 1, 0, 0, 0, 0 }
    • 轉(zhuǎn)換為每個(gè)數(shù)字應(yīng)該放置的位置索引:count: { 2, 4, 4, 5, 7, 8, 8, 8, 8, 8 }
    • 排序結(jié)果:arr: { 170, 90, 802, 2, 24, 45, 75, 66 }
  • 第二輪排序(十位):
    • 統(tǒng)計(jì)十位數(shù)字出現(xiàn)的次數(shù):count: { 0, 2, 0, 0, 1, 0, 1, 2, 1, 1 }
    • 轉(zhuǎn)換為每個(gè)數(shù)字應(yīng)該放置的位置索引:count: { 0, 2, 2, 2, 3, 3, 4, 6, 7, 8 }
    • 排序結(jié)果:arr: { 802, 2, 24, 45, 66, 170, 75, 90 }
  • 第三輪排序(百位):
    • 統(tǒng)計(jì)百位數(shù)字出現(xiàn)的次數(shù):count: { 6, 1, 0, 0, 0, 0, 0, 0, 1, 0 }
    • 轉(zhuǎn)換為每個(gè)數(shù)字應(yīng)該放置的位置索引:count: { 6, 7, 7, 7, 7, 7, 7, 7, 8, 0 }
    • 排序結(jié)果:arr: { 2, 24, 45, 66, 75, 90, 170, 802 }

在每一輪排序中,count 數(shù)組統(tǒng)計(jì)了當(dāng)前位上數(shù)字出現(xiàn)的次數(shù),并轉(zhuǎn)換為每個(gè)數(shù)字在排序后的位置索引。然后,根據(jù) count 數(shù)組的統(tǒng)計(jì)結(jié)果,將原數(shù)組中的元素按照當(dāng)前位的值放入臨時(shí)數(shù)組 tmpArr中。最后,將排好序的 tmpArr拷貝回原數(shù)組 arr,完成一輪排序。在所有位都排序完畢后,數(shù)組 arr 就是有序的。文章來源地址http://www.zghlxwxcb.cn/news/detail-509096.html

到了這里,關(guān)于【數(shù)據(jù)結(jié)構(gòu)與算法C++實(shí)現(xiàn)】3、排序算法的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【數(shù)據(jù)結(jié)構(gòu)與算法】:非遞歸實(shí)現(xiàn)快速排序、歸并排序

    【數(shù)據(jù)結(jié)構(gòu)與算法】:非遞歸實(shí)現(xiàn)快速排序、歸并排序

    ?? 個(gè)人主頁 : Quitecoder ?? 專欄 :數(shù)據(jù)結(jié)構(gòu)與算法 上篇文章我們?cè)敿?xì)講解了遞歸版本的快速排序,本篇我們來探究非遞歸實(shí)現(xiàn)快速排序和歸并排序 快速排序的非遞歸實(shí)現(xiàn)主要依賴于棧(stack)來模擬遞歸過程中的函數(shù)調(diào)用棧。遞歸版本的快速排序通過遞歸調(diào)用自身來處理子

    2024年03月24日
    瀏覽(24)
  • 【數(shù)據(jù)結(jié)構(gòu)】用Java實(shí)現(xiàn)七大排序算法

    【數(shù)據(jù)結(jié)構(gòu)】用Java實(shí)現(xiàn)七大排序算法

    目錄 ??1. 排序的概念及引用 1.1 排序的概念 1.2 衡量指標(biāo) 1.2 十個(gè)排序算法 ?1.3 十個(gè)排序性能對(duì)比 ??2. 冒泡排序 2.1 算法描述 2.2 動(dòng)圖 ??代碼優(yōu)化 ??3. 選擇排序 3.1 算法描述 3.2 動(dòng)圖 ?3.3 代碼 ??4. 插入排序 4.1 算法描述 4.2 動(dòng)圖 ?4.3 代碼 ??5 希爾排序 5.1 描述 5.2 動(dòng)圖 ?

    2023年04月23日
    瀏覽(30)
  • 數(shù)據(jù)結(jié)構(gòu)與算法——排序(C語言實(shí)現(xiàn))

    數(shù)據(jù)結(jié)構(gòu)與算法——排序(C語言實(shí)現(xiàn))

    ???????????????? ???????????????? ???????????????????????????????? ???????????????????????????????? ???? 追風(fēng)趕月莫停留 ???? ???????????????????????????????? ???? 平蕪盡處是春山

    2024年04月09日
    瀏覽(97)
  • [數(shù)據(jù)結(jié)構(gòu) -- 手撕排序算法第七篇] 遞歸實(shí)現(xiàn)歸并排序

    [數(shù)據(jù)結(jié)構(gòu) -- 手撕排序算法第七篇] 遞歸實(shí)現(xiàn)歸并排序

    目錄 1、歸并的思想 2、歸并排序的思想 2.1 基本思想 2.2 圖解分析 3、歸并排序遞歸版本代碼實(shí)現(xiàn) 3.1 代碼解析 3.2 注意事項(xiàng) 3.2.1錯(cuò)誤劃分:[begin, mid-1],[mid, end] 3.2.2 正確劃分:[begin, mid], [mid+1, end] 4、歸并排序的測(cè)試 5、時(shí)間復(fù)雜度、空間復(fù)雜度分析 5.1 時(shí)間復(fù)雜度 5.2 空間復(fù)雜

    2024年02月16日
    瀏覽(36)
  • 【算法與數(shù)據(jù)結(jié)構(gòu)】Java實(shí)現(xiàn)查找與排序

    【算法與數(shù)據(jù)結(jié)構(gòu)】Java實(shí)現(xiàn)查找與排序

    也叫做折半查找,屬于有序查找算法。 前提條件 :數(shù)組數(shù)據(jù)必須有序,從小到大,或者從大到小都是可以的。 如果是無序的,也可以先進(jìn)行排序。 但是排序之后,會(huì)改變?cè)袛?shù)據(jù)的順序,查找出來元素位置跟原來的元素可能是不一樣的,所以排序之后再查找只能判斷當(dāng)前數(shù)

    2024年01月19日
    瀏覽(31)
  • 數(shù)據(jù)結(jié)構(gòu)實(shí)驗(yàn)任務(wù)八:排序算法的實(shí)現(xiàn)與分析

    數(shù)據(jù)結(jié)構(gòu)實(shí)驗(yàn)任務(wù)八:排序算法的實(shí)現(xiàn)與分析

    問題描述 統(tǒng)計(jì)成績:給出 n 個(gè)學(xué)生的考試成績表,每條信息由姓名和分?jǐn)?shù)組成,試設(shè) 計(jì)一個(gè)算法: 1.按分?jǐn)?shù)高低次序,打印出每個(gè)學(xué)生在考試中獲得的名次,分?jǐn)?shù)相同的為同 一名次; 2.按名次列出每個(gè)學(xué)生的姓名與分?jǐn)?shù)。 輸入要求 輸入 n+1 行,前 n 行是 n 個(gè)學(xué)生的信息(姓

    2024年02月04日
    瀏覽(98)
  • 【數(shù)據(jù)結(jié)構(gòu)與算法】快速排序的三種實(shí)現(xiàn)方法

    【數(shù)據(jù)結(jié)構(gòu)與算法】快速排序的三種實(shí)現(xiàn)方法

    ? 目錄 一.基本思想 二.Hoare法 動(dòng)態(tài)演示 三.挖坑法 動(dòng)態(tài)演示 四.前后指針法 動(dòng)態(tài)演示 五.快速排序優(yōu)化 隨機(jī)下標(biāo)交換法 三路取中法 六.快速排序的特性 任取待排序元素序列中的某元素作為 基準(zhǔn)值 ,按照該排序碼將待排序集合 分割成兩子序列 , 左子序列中所有元素均小于基

    2023年04月09日
    瀏覽(31)
  • 【數(shù)據(jù)結(jié)構(gòu)與算法】快速排序的非遞歸實(shí)現(xiàn)方法

    【數(shù)據(jù)結(jié)構(gòu)與算法】快速排序的非遞歸實(shí)現(xiàn)方法

    ? 目錄 一.前言 二.非遞歸實(shí)現(xiàn) 如果數(shù)據(jù)量過大的話,不斷遞歸就會(huì)出現(xiàn) 棧溢出 的現(xiàn)象,這個(gè)時(shí)候你的代碼是沒問題的,但就是跑不起來,這個(gè)時(shí)候就要 把遞歸改成非遞歸 。 一般有兩種改法: 1.直接改,利用循環(huán)等; 2.借助棧的輔助。 而快速排序的非遞歸實(shí)現(xiàn)方法就需要

    2023年04月17日
    瀏覽(22)
  • 數(shù)據(jù)結(jié)構(gòu)與算法中的七大排序(Java實(shí)現(xiàn))

    數(shù)據(jù)結(jié)構(gòu)與算法中的七大排序(Java實(shí)現(xiàn))

    目錄 一、直接插入排序 二、希爾排序 三、直接選擇排序 四、堆排序 五、冒泡排序 六、快速排序 七、歸并排序 ? ? ? ? ? ? ? 定義i下標(biāo)之前的元素全部已經(jīng)有序 ,遍歷一遍要排序的數(shù)組,把i下標(biāo)前的元素全部進(jìn)行排序,當(dāng)遍歷玩這個(gè)數(shù)組后,就已經(jīng)排好序了。 ? ? ? ?

    2024年02月06日
    瀏覽(31)
  • 數(shù)據(jù)結(jié)構(gòu)和算法——快速排序(算法概述、選主元、子集劃分、小規(guī)模數(shù)據(jù)的處理、算法實(shí)現(xiàn))

    數(shù)據(jù)結(jié)構(gòu)和算法——快速排序(算法概述、選主元、子集劃分、小規(guī)模數(shù)據(jù)的處理、算法實(shí)現(xiàn))

    目錄 算法概述 圖示 偽代碼 選主元 子集劃分 小規(guī)模數(shù)據(jù)的處理 算法實(shí)現(xiàn) 快速排序和歸并排序有一些相似,都是用到了分而治之的思想: ? 通過初步的認(rèn)識(shí),我們能夠知道快速排序算法最好的情況應(yīng)該是: 每次都正好中分 ,即每次選主元都為元素的中位數(shù)的位置。 最好情

    2024年02月15日
    瀏覽(21)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包