【C】八大排序

參考:十大排序

 

目錄

一.交換排序

1.冒泡排序

雙冒泡排序 

2.快速排序

二.選擇排序

1.簡單選擇排序

2.堆排序

三.插入排序

1.直接插入排序

2.希爾排序


 

一.交換排序

1.冒泡排序

時間複雜度:n^2 (n / n^{2}

空間複雜度:1

穩定

思想:

  • 比較相鄰兩個元素大小,左邊大就交換
  • 每次再從第一個開始比較,第一次比較完最大的在隊尾
  • 比較length-1次
void BubbleSort(int *arr,int len)
{
	int i, j;
	int temp;

	for(i = 0; i < len - 1; i++)
	{
		for(j = 0; j < len - i - 1; j++)
		{
			if(arr[j] > arr [j + 1])
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

雙冒泡排序

思想:

  • 每輪都進行兩次排序,一次從低到高,將最大的排到最右端,再從高到底,把最小的排到最左端。(當然每一次都是冒泡排序)
void BubbleSort2(int *arr, int len)
{
	int i, left, right, temp;

	left = 0;
	right = len - 1;
	while(left < right)
	{
		for(i = left; i < right; i++)
		{
			if(arr[i] > arr[i + 1])
			{
				temp = arr[i];
				arr[i] = arr[i + 1];
				arr[i + 1] = temp;
			}
		}
		right--;

		for(i = right; i > left; i--)
		{
			if(arr[i] < arr[i - 1])
			{
				temp = arr[i];
				arr[i] = arr[i - 1];
				arr[i - 1] = temp;
			}
		}
		left++;
	}
}

雙冒泡排序改進版:

再設一個左標兵 l 和右標兵 r ,記錄一下萬一排序有幾個已經排好了,就不用在排了。但一開始要設個初值,萬一正好是個完全有序的數列,不設初值會出錯。

void BubbleSort3(int *arr, int len)
{
	int i, left, right, l, r, temp;

	left = 0;
	right = len - 1;
	l = right;
	r = left;

	while(left < right)
	{
		for(i = left; i < right; i++)
		{
			if(arr[i] > arr[i + 1])
			{
				temp = arr[i];
				arr[i] = arr[i + 1];
				arr[i + 1] = temp;
				r = i;
			}
		}
		right = r;

		for(i = right; i > left; i--)
		{
			if(arr[i] < arr[i - 1])
			{
				temp = arr[i];
				arr[i] = arr[i - 1];
				arr[i - 1] = temp;
				l = i;
			}
		}
		left = l;
	}

}

 

2.快速排序

時間複雜度:nlog_{2}n (nlog_{2}nn^2

 log_{2}n的出處?:

最好情況:每次都恰好五五分,一次遞歸共需比較n次,遞歸深度爲log_{2}n

綠底的解釋(有具體公式推導)

巧思

簡單清楚一點的公式推導

空間複雜度:nlog_{2}n

不穩定

思想:

  • 將第一個數作爲基準數,將比它小的放它左邊,比它大的放它右邊
  • 分別從頭(i++)和從尾(j--)依次遍歷,直到i = j爲止
  • 在對基準數的左右兩邊再重複這樣的過程(分治法)
void QuickSort(int *arr,int left,int right)
{
	if(left >= right)
		return;

	int i = left;
	int j = right;
	int key = arr[i];

	while(i < j)
	{
		while(i < j && key <= arr[j])
		{
			j--;
		}

		arr[i] = arr[j];

		while(i < j && key >= arr[i])
		{
			i++;
		}

		arr[j] = arr[i];
	}

	arr[i] = key;
	QuickSort(arr,left,i-1);
	QuickSort(arr,i+1,right);
}

二.選擇排序

1.簡單選擇排序

時間複雜度:n^2 (n^2n^2

表現最穩定的排序算法之一,因爲無論什麼數據進去都是O(n2)的時間複雜度,所以用到它的時候,數據規模越小越好。

空間複雜度:1

不穩定

思想:

  • 從待排序序列中,找到關鍵字最小的元素
  • 如果最小元素不是待排序序列的第一個元素,將其和第一個元素互換
  • 從餘下的 N - 1 個元素中,找出關鍵字最小的元素,重複(1)、(2)步,直到排序結束
  • void SelectionSort(int *arr,int len)
    {
    	int i, j, k, temp;
    
    	for(i = 0; i < len - 1; i++)
    	{
    		k = i;
    		for(j = i + 1; j < len; j++)
    		{
    			if(arr[j] < arr[k])
    			{
    				k = j;
    			}
    		}
    
    		if(k != i)
    		{
    			temp = arr[i];
    			arr[i] = arr[k];
    			arr[k] = temp;
    		}
    	}
    }

     

2.堆排序

能實現從海量數據中找出k個最大或最小值,創建一個結點爲k的樹即可,每次跟裏面的樹比較,大於就放進去,小於就下一個。但插入排序不行,如果最大數最後纔出現,那麼不到最後一步完成,不敢保證說前面k個就是答案了。

面對這種問題,快速排序歸併排序也可以解決。

時間複雜度:nlog_{2}n (nlog_{2}nnlog_{2}n

堆排序可分細分爲兩部分:建堆過程+排序過程。建堆過程時間複雜度爲O(n),即將一個無序數組建立成堆只需要線性時間。排序過程需要對n個數據進行篩選時,每次篩選需要O(logn)時間,所以整個排序過程的時間爲O(nlogn)因此堆排序總的運行時間爲: O(nlogn) = O(n) + O(nlogn)

空間複雜度:nlog_{2}n

不穩定

什麼是堆:

任意的葉子節點小於(或大於)它所有的父節點。對此,又分爲大頂堆和小頂堆,大頂堆要求節點的元素都要大於其孩子,小頂堆要求節點元素都小於其左右孩子,兩者對左右孩子的大小關係不做任何要求。

思想:

  • 首先將序列構建稱爲大頂堆
  • 取出當前大頂堆的根節點,將其與序列末尾元素進行交換
  • 對交換後的n-1個序列元素進行調整,使其滿足大頂堆的性質
  • 重複2.3步驟,直至堆中只有1個元素爲止

可以只看圖的講解,不用看代碼

這個圖講解的更詳細一點,如果還不懂可以看一下

void MaxHeap(int *arr, int start, int end)
{
	int dad = start;		//父節點
	int son = dad * 2 + 1;	//子節點(樹的根從編號0開始算)
	int temp;

	while(son <= end)	//如果子節點在範圍之外就結束排序
	{
		//先比較兩個子節點大小,選出最大的子節點
		if(son + 1 <= end && arr[son] < arr[son + 1])
			son++;
		//如果父節點比最大的子節點大,則沒有再排序的必要
		if(arr[dad] > arr[son])
			return;
		//否則交換內容,再對孫節點繼續比較,糾正這個交換帶來的影響
		else
		{
			temp = arr[dad];
			arr[dad] = arr[son];
			arr[son] = temp;
			dad = son;
			son = dad * 2 + 1;
		}
	}
}

void HeapSort(int *arr,int len)
{
	int i, temp;

	//這整個循環先將整個數組都變成一個大頂堆
	//len/2-1 就是最後一個父節點的位置(注意這邊樹的編號從0開始)
	for(i = len / 2 - 1; i >= 0; i--)
	{
		MaxHeap(arr, i, len - 1);
	}

	//每一次循環都將大頂堆最大的數和末尾的數進行交換
	//再對剩下的數進行大頂堆排序
	//最後一個不用排
	for(i = len - 1; i > 0; i--)
	{
		temp = arr[0];
		arr[0] = arr[i];
		arr[i] = temp;
		MaxHeap(arr, 0, i - 1);	//下一輪大頂堆排序的範圍
	}
}

三.插入排序

1.直接插入排序

時間複雜度:n^2 (nn^2

空間複雜度:1

穩定

思想:

  • 將數組中的所有元素依次跟前面已經排好的元素相比較,如果選擇的元素比已排序的元素小,則交換,直到全部元素都比較過。
  • 從第一個元素開始,該元素可以認爲已經被排序;
  • 取出下一個元素,在已經排序的元素序列中從後向前掃描;
  • 如果該元素(已排序)大於新元素,將該元素移到下一位置;
  • 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置;
  • 將新元素插入到該位置後;
void InsertSort(int *arr,int len)
{
	int i, j, temp;

	for(i = 1; i < len; i++)
	{
		temp = arr[i];
		for(j = i - 1; j >= 0; j--)
		{
			if(arr[j] > temp)
			{
				arr[j + 1] = arr[j];
			}
			else
			{
				break;
			}
		}
		arr[j + 1] = temp;
	}
}

2.希爾排序

時間複雜度:n^{1.3} (nn^2

空間複雜度:1

不穩定

思想:

  • 將待排序數組按照步長gap進行分組,然後將每組的元素利用直接插入排序的方法進行排序;每次將gap折半減小,循環上述操作;當gap=1時,利用直接插入,完成排序。
void ShellSort(int *arr,int len)
{
	int i, j, k, gap, temp;

	for(gap = len / 2; gap > 0; gap /= 2)
	{
		for(i = 0; i < gap; i++)	//確定執行多少次直接選擇排序
		{
			for(j = i + gap; j < len; j += gap)		//執行分組後的單個的選擇排序
			{
				temp = arr[j];
				for(k = j - gap; k >= 0; k -= gap)
				{
					if(arr[k] > temp)
					{
						arr[k + gap] = arr[k];
					}
					else
					{
						break;
					}
				}
				arr[k + gap] = temp;
			}

		}
	}
}

再簡化版:

void ShellSort2(int *arr,int len)
{
	int i, j, k, gap, temp;

	for(gap = len / 2; gap > 0; gap /= 2)
	{
		for(i = gap; i < len; i++)	//確定執行多少次直接選擇排序
		{
			temp = arr[i];		//從這步開始簡化了方法一中的j和k的循環
			for(j = i - gap; j >=0 && temp < arr[j]; j -= gap)
			{
				arr[j + gap] = arr[j];
			}
			arr[j + gap] = temp;
		}
	}
}

四.歸併排序

思想:

  • 利用遞歸與分治的思想
  • 把長度爲n的輸入序列分成兩個長度爲n/2的子序列;
  • 對這兩個子序列分別採用歸併排序,直到劃分爲長度爲1的子序列
  • 將每兩個相鄰的長度爲1的子序列進行歸併,得到n/2(向上取整)個長度爲2或者1的有序子序列,再將其兩兩歸併,反覆執行此過程,直到得到一個有序序列。
  • 需要額外存儲空間(之後的幾個應該都需要)

void Merge(int* arr, int left, int mid,int right)
{
    int low = left; //左半部分起始位置
    int hight = mid + 1; //右半部分起始位置
    int length = right - left + 1;
 
    vector<int> temp(length);//輔助數組
    int index = 0; // 輔助數組的下標
    while (low <= mid && hight <= right)
    { //挑選兩部分中最小的元素放入輔助數組中
        if (arr[low] < arr[hight])
            temp[index++] = arr[low++];
        else
            temp[index++] = arr[hight++];
    }
//如果還有剩餘,直接放入到輔助數組中
    while (low <= mid)
        temp[index++] = arr[low++];
    while (hight <= right)
        temp[index++] = arr[hight++];
//更新原始數組元素
    for (int i = 0; i < length; i++)
    {
        arr[left + i] = temp[i];
    }
}
 
void MergeSort(int* arr, int left, int right)
{
    if (left < right)
    {
        int mid = (left + right) / 2; //分割序列
        MergeSort(arr, left, mid); //對序列左半部分進行歸併排序
        MergeSort(arr, mid + 1, right); //對序列右半部分進行歸併排序
        Merge(arr, left, mid, right); //合併已經有序的兩個序列
    }
} 

 

五.桶排序

思想:

  • .設置一個定量的數組當作空桶
  • 將待排序元素劃分到不同的桶。先掃描一遍序列求出最大值 maxV 和最小值 minV ,設桶的個數爲 k ,則把區間 [minV, maxV] 均勻劃分成 k 個區間,每個區間就是一個桶。將序列中的元素分配到各自的桶。(分成不同桶就好,至於規則自己定)
  • 對每個桶內的元素進行排序。可以選擇任意一種排序算法。
  • 將各個桶中的元素合併成一個大的有序序列。
  • 桶排序的時間複雜度就可以近似認爲是 O(n) 的。即桶越多,時間效率就越高,而桶越多,空間就越大。
     

 

六.基數排序

思想:

  • 有點像桶排序
  • 現根據個位大小,都是1的放一個桶,都是2的放一個桶,(桶內不用排序?按先後進就好?)然後把他們從前到後按順序放進數組
  • 再根據十位大小,百位,依次進行

七.計數排序

思想:

  • 最大值是k,最小數是m,就分k - m + 1個桶,每個桶裏計算重複出現的數據的個數,然後根據這個頻率新建一個數組

不過計數排序只能用在數據範圍不大的情況,如果數據範圍 k 比要排序的數據 n 大很多,就不適合用計數排序了。而且計數排序只能給非負整數排序,如果要排序的數據是其他類型的,要講其在不改變相對大小的情況下,轉化爲非負整數。

八.查找方式

  1. 順序查找
  2. 二分查找
  3. 差值查找

排序與查找參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章