排序演算法(天勤資料結構高分筆記)
排序演算法
直接插入排序演算法:每趟將一個待排序的關鍵字按照其值的大小插入到已經排好的部分有序序列的適當位置上,直到所有待排關鍵字都被插入到有序序列中為止
void InsertSort(int R[], int n) //代拍關鍵字儲存在R[]中,預設為整形,個數為n { int i = 0, j = 0; int temp = 0; for (i = 1; i < n; ++i) { temp = R[i]; //將待插入關鍵字暫存於temp中 j = i + 1; //下面這個迴圈完成待排關鍵字之前的關鍵字開始掃描,如果大於待排關鍵字,則後移一位 while (j >= 0 && temp < R[j]) { R[j + 1] = R[j]; --j; } R[j + 1] = temp; //找到插入位置,將temp中暫存的待排關鍵字插入 } } //直接插入演算法的時間複雜度為O(n*n)
希爾排序:希爾排序又稱之為縮小增量排序,其本質還是插入排序,只不過是將待排序列按照某種規則分成幾個子序列
,分別對這幾個子序列進行直接插入排序。這個規則的體現就是增量的選取.希爾排序的時間複雜度為O(n*logn)
void Shellsort(int Array[], int n) { int d = n / 2; //設定起始增量 while (d >= 1) //增量為1時排序結束 { for (int k = 0; k < d; ++k) //遍歷所有的子序 { for (int i = k + d; i < n; i += d) //對每個子序進行插入排序 { int temp = Array[i]; int j = i - d; while (j >= k && Array[j] > temp) { Array[j + d] = Array[j]; j -= d; } Array[j + d] = temp; } } d = d / 2; //縮小增量 } }
//氣泡排序:時間複雜度為O(n*n) void BubbleSort(int R[], int n) //預設待排序關鍵字為整型 { int i = 0, j = 0, flag = 0; int temp; for (int i = n - 1; i >= 1; --i) { flag = 0; //變數flag用來標記本堂排序是否發生了交換 for (j = 0; j < i; ++j) if (R[j - 1] > R[j]) { temp = R[j]; R[j] = R[j - 1]; R[j - 1] = temp; flag = 1; //如果沒有發生交換,則flag的值為0,;如果發生了交換,flag的值改為1 } if (0 == flag) //一趟排序過程中如果沒有發生關鍵字交換,則證明序列有序,排序結束 return; } }
快速排序;也是交換類的排序,它通過多次劃分操作實現排序。以升序為例,其執行流程可以概括為:每一趟選擇當前所有子序列中的一個關鍵字(通常是第一個)作為樞軸,將子序列中比樞軸小的移到樞軸的前邊,比樞軸大的移動到樞軸的後邊;當本趟所有的子序列都被樞軸以上述規則劃分完畢後會的到新的一組更短的子序列,它們成為下一趟劃分的初始序列集。快速排序的演算法思想基於分治思想的,其平均時間複雜度為O(n*logn),最壞時間複雜度為O(n*n)
void QuickSort(int R[], int low, int high) //對從R[Low]到R[High]的關鍵字進行排序
{
int temp = 0;
int i = low, j = high;
if (low < high)
{
temp = R[low];
//下面這個迴圈完成了一趟排序,即陣列中小於temp的關鍵字放在左邊,大於temp的關鍵字放在右邊。左邊和右邊的分界點就是temp的最終位置
while (i < j)
{
while (i < j && R[j] >= temp) //先從右往左掃描,找到第一個小於temp的關鍵字
--j;
if (i < j) //這個判斷保證退出上面的while迴圈是因為R[j] < temp,而不是因為 i>= j退出迴圈的,此步非常重要切忌將其忽略
{
R[i] = R[j]; //放在temp左邊
++i; //i右移一位
}
while (i < j && R[i] <= temp) //從右往左掃描,找到一個大於temp的關鍵字
++i;
if (i < j)
{
R[j] = R[i]; //放在tem的左邊
--j; //j右移一位
}
}
R[j] = temp; //將temp放在最終的位置上
QuickSort(R, low, i - 1); //遞迴的對temp左邊的關鍵字進行排序
QuickSort(R, i + 1, high); //遞迴的對temp右邊的關鍵字進行排序
}
}
簡單選擇類排序:選擇類排序的主要動作是“選擇”。簡單選擇採用最簡單的選擇方式,從頭至尾掃描序列,選出
最小的一個關鍵字,和第一個關鍵字交換,接著從剩下的關鍵字中繼續這種選擇和交換,最終使序列有序
void SelectSort(int R[], int n)
{
int i = 0, j = 0, k = 0;
int temp = 0;
for (i = 0; i < n; ++i)
{
k = i;
//下面這個迴圈是演算法的關鍵,它從序列中挑選出最小的一個關鍵字
for (j = i + 1; j < n; ++j)
{
if (R[k] > R[j])
k = j;
}
//下面三句完成最小關鍵字與無序序列的第一個關鍵字的交換
temp = R[i];
R[i] = R[k];
R[k] = temp;
}
}
堆排序:對是一種完全二叉樹,這顆二叉樹滿足:任何一個非葉結點的值都不大於(或小於)其左右孩子結點的值。若父親大孩子小,這樣的堆稱之為大頂堆;若父親小孩子大稱為小根堆。
根據堆的定義可以知道,代表堆的這顆完全二叉樹的根結點是最大的(或者最小的),因此將一個無序的序列調整為一個堆,就可以找到這個序列的最大值(或者最小)的值,然後將找出的值交換到這個序列的最後(或最前),這樣有序序列關鍵字增加1個,無序序列中的關鍵字減少1個,對新的無序序列重複這樣的操作,就實現了排序。這就是堆排序的思想
堆排序中最關鍵的操作是將序列調整為堆。整個排序的過程就是通過不斷調整,使得不符合堆定義的完全二叉樹變為符合堆定義的完全二叉樹
堆的插入關鍵字:需要在插入結點之後保持堆的性質,即完全二叉樹形態與父大子小性質(以大根堆為例),因此需要先將要插入的結點X放在最底層的最右邊,插入後滿足完全二叉樹的特點,然後把X依次向上調整到合適位置上以滿足父大子小的性質
堆中刪除結點:刪除堆中一個結點時,原來的位置就會出現一個孔,填充這個孔的方法就是:把最底層最右邊的葉子值賦給該孔並下調到合適的位置,最後把
該葉子結點點刪除堆排序執行過程描述(以大根堆為例):
1)從無序序列所確定的完全二叉樹的第一個非葉子結點開始,從左至右,從上至下,對每個結點進行調整,最終得到一個大根堆
對結點的調整方法:將當前結點(假設為A)的值與其孩子結點進行比較,如果存在大於A的值的孩子結點,則從中挑出最大的一個與A進行交換。當A來到
下一層的時候重複上述過程,直到A的孩子結點的值都小於A的值為止。
2)將當前的無序序列中的第一個關鍵字,反應在樹的根結點(假設為B)與無序序列中的最後一個關鍵字交換(假設為C)。B進入有序序列,達到最終位置。
無序序列中的關鍵字個數減少1個,有序序列中的關鍵字個數增加1個,此時只有結點C可能不滿足堆的定義,對其進行調整
3)重複上述第2)步,直到無序序列中的關鍵字個數為1時結束排序
程式碼如下:
//本函式完成在陣列R[Low]到R[High]的範圍內對在位置Low上的結點進行調整
void Sift(int R[], int Low, int High)//這裡關鍵字的儲存設定為從陣列下標為1開始
{
int i = Low, j = 2 * i; //R[j]是R[i]的左孩子
int temp = R[i];
while (j <= High)
{
if (j < High && R[j] < R[j + 1]) //若右孩子較大,則把j指向右孩子
++j; //j變為 2*i+1
if (temp < R[j])
{
R[i] = R[j]; //將R[j]調整到雙親結點的位置上
i = j;
j = 2 * i; //修改i和j的值,以便繼續向下調整
}
else break;
}
R[i] = temp; //被調整結點的值放入最終位置
}
//堆排序函式
void HeapSort(int R[], int n)
{
int i = 0;
int temp = 0;
for (i = n / 2; i >= 1; --i) //初始化堆
Sift(R, i, n);
for (i = n; i >= 2; --i) //進行n-1次迴圈完成堆排序
{
//下面三句換出了根結點中的關鍵字,將其放入最終的位置
temp = R[1];
R[1] = R[i];
R[i] = temp;
Sift(R, 2, i - 1); //在減少了1個關鍵字的無序序列中進行調整
}
堆排序演算法所需的空間複雜度為O(1),這是它相對於歸併排序的優點。時間複雜度在任何情況下均為O(n*logn),這是它相對於快速排序的最大優點, 快速排序最壞的時間複雜度為O(n*n)。
堆排序適用場景是關鍵字數目特別多的情況下,典型的例子是從10000個關鍵字選出前10個最小的。這種情況下用堆排序最好。如果關鍵字數目較少,則不建議使用堆排序
//STL中已經寫好了堆排序,一般如果是自己在實踐中需要用的是由直接呼叫下面兩句即可
make_heap(_First, _Last, _Comp); //預設是建立最大堆的。對int型別,可以在第三個引數傳入greater<int>()得到最小堆。
sort_heap(_First, _Last); //排序之後就不再是一個合法的heap了
make_heap(b, b + 10 ,greater<int> ()); //建立小根堆
sort_heap(b, b + 10, greater<int>()); //從大到小進行排序
make_heap()建堆的時候,預設是大根堆,第三個引數用greater<T>會變成小根堆;
sort_heap()排序的時候,預設是從小到大,但是第三個引數用greater<T>會變成從大到小
並且sort_heap的第三個引數要和make_heap的第三個引數一致,否則程式執行時會報錯
//上面這兩個函式原型為:
template <class RandomAccessIterator>
void make_heap (RandomAccessIterator first, RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
void make_heap (RandomAccessIterator first, RandomAccessIterator last,
Compare comp );
template<class _RanIt> inline
void sort_heap(_RanIt _First, _RanIt _Last)
{ // order heap by repeatedly popping, using operator<
_STD sort_heap(_First, _Last, less<>());
}
*/