C++數據結構與算法--常見排序算法(冒泡、快速、歸併、插入、選擇、希爾排序、堆...)
本文由博主經過查閱網上資料整理總結後編寫,如存在錯誤或不恰當之處請留言以便更正,內容僅供大家參考學習。
冒泡排序
主要思想:反覆調用單趟掃描交換算法,直至逆序現象完全消除(依次比較兩個數大小,較大的數下沉,較小的數冒起來,獲得單趟掃描最大值放到數組尾部,繼續循環)
過程理解:
比較相鄰的兩個數據,如果第一個數大,就交換位置。
從前向後兩兩比較,一直到比較最後兩個數據。最終最大數被交換到末尾的位置。
繼續重複上述過程,依次將第2.3...n-1個最大數排好位置。
性能分析:
N個數總共進行N-1趟排序,第i趟的排序次數爲(N-i)次,用雙重循環語句,外層控制循環多少趟,內層控制每一趟的循環次數。
(1) 時間複雜度
當原始序列雜亂無序時,冒泡排序的平均時間複雜度爲O(n^2)。
當原始序列“正序”排列時,冒泡排序總的比較次數爲n-1,移動次數爲0,在最好情況下的時間複雜度爲O(n);
當原始序列“逆序”排序時,冒泡排序總的比較次數爲n(n-1)/2,移動次數爲3n(n-1)/2次,最壞情況下的時間複雜度爲O(n^2);
(2) 空間複雜度
冒泡排序排序過程中需要一個臨時變量進行兩兩交換,所需要的額外空間爲1,因此空間複雜度爲O(1)。
(3) 穩定性
冒泡排序在排序過程中,元素兩兩交換時,相同元素的前後順序並沒有改變,所以冒泡排序是一種穩定排序算法。
C++實現代碼:
/*-----------------------數組實現-----------------------------------*/
template<typename T>
void bubble_sort(T arr[], int len) {
int i, j;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1])
swap(arr[j], arr[j + 1]);
}
/*-----------------------vector實現-----------------------------------*/
template<typename T>
void bubble_sort(vector<T> &arr, int len) {
int i, j;
for (i = 0; i < len - 1; i++)
for (j = 0; j < len - 1 - i; j++)
if (arr[j] > arr[j + 1])
swap(arr[j], arr[j + 1]);
}
改進版本:添加每次循環進行一次是否已經有序判斷,如果有序了就直接返回
//改進版本
template<typename T>
void bubble_sort(T arr[], int len) {
int i, j;
for (i = 0; i < len - 1; i++) { //控制循環的次數
bool sorted = true; //整體有序標誌
for (j = 0; j < len - 1 - i; j++) //內循環,找出最大數
if (arr[j] > arr[j + 1]) {
sorted = false; //記錄存在逆序
swap(arr[j], arr[j + 1]);
}
if (sorted) break; //如果都有序則直接結束
}
}
選擇排序
主要思想:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
過程理解:
比較未排序區域的元素,選出最大或最小的元素放到排序區域。
一趟比較完成之後,再從剩下未排序的元素開始比較。
反覆執行以上步驟,n-1趟結束,數組實現有序化。
性能分析
時間複雜度:最佳情況:T(n) = O(n2) 最差情況:T(n) = O(n2) 平均情況:T(n) = O(n2),用到它的時候,數據規模越小越好
空間複雜度:不佔用額外的內存空間O(1)
穩定性:不穩定,舉個例子,序列5 8 5 2 9, 我們知道第一遍選擇第1個元素5會和2交換,那麼原序列中2個5的相對前後順序就被破壞了,所以選擇排序不是一個穩定的排序算法
C++代碼實現
template<typename T>
void selectSort(vector<T> &arr) {
if (arr.size() <= 1) return;
//進行n-1趟循環
for (int i = 0; i < arr.size() - 1; i++) {
//每趟循環找出無序序列中最小的元素放到有序序列後面
for (int j = i + 1; j < arr.size(); j++)
if (arr[i] > arr[j]) swap(arr[i], arr[j]);
}
}
template<typename T>
void selectSort(T arr[],int len) {
if (len <= 1) return;
//進行n-1趟循環
for (int i = 0; i < len - 1; i++) {
//每趟循環找出無序序列中最小的元素放到有序序列後面
int min = i; //記錄本次選擇目標最小元素的下標
for (int j = i + 1; j < len; j++)
if (arr[j] < arr[min]) min = j;
swap(arr[i], arr[min]);
}
}
插入排序
主要思想:通過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入,需要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。
過程理解:
第1趟插入:將第2個元素插入前面的有序子序列,此時前面只有一個元素,當然是有序的
第2趟比較:將第3個元素插入前面的有序子序列,前面的2個元素是有序的
......
第n-1趟比較:將第n個元素插入前面的有序子序列,前面n-1個元素是有序的
性能分析:
(1) 時間複雜度:最好O(n), 最壞 O(n2), 平均O(n2)
(2) 空間複雜度:O(1)
(3) 穩定性:穩定
插入排序在對幾乎已經排好序的數據操作時,效率高,即可以達到線性排序的效率;比較適合於數據量較小的序列排序
C++實現代碼:
template<typename T>
void insertSort(vector<T> &arr) {
//從第二個數開始判斷插入到有序序列中,直到最後一個數
for (int i = 1; i < arr.size(); i++) {
int temp = arr[i];
int j;
//爲a[i]在前面的arr[0...i-1]有序區間中找一個合適的位置
for (j = i - 1; j >= 0; j--) {
//將大於temp的數向後移動一位
if (temp<arr[j]) {
arr[j + 1] = arr[j];//記錄j的值也就是temp要插入的位置
}
else break;
}
arr[j+1] = temp; //將值插入序列特定位置
}
}
template<typename T>
void insertSort(vector<T> &arr) {
//從第二個數開始判斷插入到有序序列中,直到最後一個數
for (int i = 1; i < arr.size(); i++) {
//爲a[i]在前面的arr[0...i-1]有序區間中找一個合適的位置
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr[j], arr[j+1]);
}
}
}
優化:在0~i-1個有序元素給第i個元素尋找插入的位置時,使用二分查找法可以有效提高查找插入位置的時間效率,經過優化的插入排序稱爲折半插入排序
template<typename T>
void binaryInserSort(vector<T> &arr) {
//從第二個數開始判斷插入到有序序列中,直到最後一個數
for (int i = 1; i < arr.size(); i++) {
int tmp = arr[i];
int left = 0;
int right = i - 1;
// 通過二分查找找到插入的位置
while (left <= right) {
int mid = (left + right) / 2;
//如果中間元素大於tmp,則目標位置在中間位置的右側
if (tmp > arr[mid]) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
// 插入位置之後的元素依次向後移動
for (int j = i; j > left; j--) {
arr[j] = arr[j - 1];
}
// 更新插入位置的值
arr[left] = tmp;
}
}
希爾排序
主要思想:先將整個待排序的記錄序列分割成爲若干子序列分別進行直接插入排序,此時,插入排序所作用的數據量比較小(每一個小組),插入的效率比較高,待整個序列中的記錄"基本有序"時,再對全體記錄進行依次直接插入排序。(插入排序改進)https://blog.csdn.net/qq_39207948/article/details/80006224
過程理解
選擇一個增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
按增量序列個數 k,對序列進行 k 趟排序;
每趟排序,根據對應的增量 ti,將待排序列分割成若干長度爲 m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲 1 時,整個序列作爲一個表來處理,表長度即爲整個序列的長度。
性能分析
時間複雜度:最佳情況:T(n) = O(nlogn) 最差情況:T(n) = O(nlog²n) 平均情況:T(n) = O(nlog²n)
穩定性:不穩定
C++代碼實現
template<typename T>
void shell_sort(vector<T> &arr, int len) {
//拆分整個序列,元素間距爲gap(也就是增量)
for (int gap = len / 2; gap >= 1; gap = gap / 2) {
//對子序列進行直接插入排序
for (int i = gap; i < len; i++) {
for (int j = i - gap; j >= 0 && arr[j] > arr[j + gap]; j = j - gap) {
swap(arr[j], arr[j + gap]);
}
}
}
歸併排序
主要思想:各層分治遞歸調用二路歸併算法,直至逆序現象完全消除(將兩個有序序列合併爲一個有序序列)
注:二路歸併屬於迭代算法,每步迭代中,只需比較帶歸併序列的首元素,將小者取出並追加到輸出序列的尾部,該元素在原序列中的後繼稱爲新的首元素。如此往復,直至某一序列爲空。最後,將另一非空的序列整體接在輸出序列的末尾。
過程理解:
把長度爲n的輸入序列分成兩個長度爲n/2的子序列;
對這兩個子序列分別採用二路歸併排序;
將兩個排序好的子序列合併成一個最終的排序序列。
性能分析:
(1) 時間複雜度
設數列長爲N,將數列分開成小數列一共要logN步,每步都是一個合併有序數列的過程,時間複雜度可以記爲O(N),故一共均需要複雜度O(N*logN)
(2) 空間複雜度
空間複雜度:O(N),歸併排序需要一個與原數組相同長度的數組做輔助來排序
(3) 穩定性
歸併排序是穩定的排序算法,temp[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
這行代碼可以保證當左右兩部分的
值相等的時候,先複製左邊的值,故爲穩定算法
C++實現代碼:
template<typename T>
void merge(vector<T> &arr, int L, int mid, int R) { //有序向量的逐層歸併
vector<int> temp; //臨時變量用來存放本次合併後的數組
int p1 = L;
int p2 = mid + 1;
// 比較左右兩部分的元素,哪個小,把那個元素填入temp中
while (p1 <= mid && p2 <= R) {
temp.push_back(arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]);
}
// 上面的循環退出後,把剩餘的元素依次填入到temp中,只有一個while會執行
while (p1 <= mid) {
temp.push_back(arr[p1++]);
}
while (p2 <= R) {
temp.push_back(arr[p2++]);
}
// 把最終的排序的結果複製給原數組
for (int i = 0; i < temp.size(); i++) {
arr[L + i] = temp[i];
}
}
template<typename T>
void mergeSort(vector<T> &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);
}
快速排序
主要思想:採用雙向查找的策略,每一趟選擇當前子序列中的一個數作爲key值,將子序列中比key值小數放在它的左邊,大於或等於它的數全部放在它的右邊;一趟排序完成後實現將要排序的數據按照key爲分割點劃分成獨立的兩個子序列。接着分別對這兩部分記錄繼續進行排序,以達到整個序列有序。http://www.sohu.com/a/246785807_684445;https://blog.csdn.net/nrsc
過程理解(挖坑填數):
定義臨時變量tmp存儲基準數據key;然後分別從數組的兩端掃描數組,設兩個指示標誌:low指向起始位置,high指向末尾.。
從後半部分開始,如果掃描到的值大於基準數據就讓high減1,若有元素比該基準數據的值小,就將high位置的值賦值給low位置
接着從前往後,如果掃描到的值小於基準數據就讓low加1,若有元素大於基準數據的值,就再將low位置的值賦值給high位置
然後再從後向前,原理同上,直到low與high位置重合,此時low或high的下標即基準數據key在該數組中的正確索引位置
性能分析:
(1) 時間複雜度
快速排序的平均時間複雜度也是:O(nlogn)
最優時間複雜度:O(nlogn)
最差的情況就是每一次取到的元素就是數組中最小/最大的,該情況下時間複雜度爲:O( n^2 )
(2) 空間複雜度
快速排序在每次分割的過程中,需要 1 個空間存儲基準值。而快速排序的大概需要 Nlog2N次的分割處理,所以佔用空間也是 Nlog2N 個。
(3) 穩定性
在快速排序中,相等元素可能會因爲分區而交換順序,所以它是不穩定的算法。
C++實現代碼:
//尋找基準值
template<typename T>
int getBase(vector<T> &arr, int low, int hi) {
// 以最左邊的數(left)爲基準
T tmp = arr[low];
while (low < hi) {
// 從序列右端開始,向左遍歷,直到找到小於base的數
while (low < hi&&arr[hi] >= tmp) {
hi--;
}
// 找到了比base小的元素,將這個元素放到最左邊的位置
arr[low] = arr[hi];
// 從序列左端開始,向右遍歷,直到找到大於base的數
while (low < hi&&arr[low] <= tmp) {
low++;
}
// 找到了比base大的元素,將這個元素放到最右邊的位置
arr[hi] = arr[low];
}
// 跳出循環時low和high相等,此時的low或high就是tmp的正確索引位置
arr[low] = tmp;
return low;
}
//快速排序
template<typename T>
void quickSort(vector<T> &arr, int low, int hi) {
// 左下標一定小於右下標,否則就越界了
if (low < hi) {
// 對數組進行分割,取出下次分割的基準標號
int base = getBase(arr, low, hi);
// 對“基準標號“左右側的數值分別進行遞歸的切割,以至於將這些數值完整的排序
quickSort(arr, low, base - 1);
quickSort(arr, base + 1, hi);
}
}
//簡化版本
template<typename T>
void quickSort(T arr[], int low, int hi)
{
int s_low = low;
int s_hi = hi;
if (low < hi){
int temp = arr[low];
while (s_low < s_hi)
{
//從序列右端開始,向左遍歷,直到找到小於base的數
while (s_hi > s_low && arr[s_hi] >= temp) s_hi--;
//找到比基準值小的元素,將這個元素放到最左邊的位置
arr[s_low] = arr[s_hi];
// 從序列左端開始,向右遍歷,直到找到大於base的數
while (s_low < s_hi && arr[s_hi] < temp) s_low++;
//找到了比base大的元素,將這個元素放到最右邊的位置
arr[s_hi] = arr[s_low];
}
arr[s_low] = temp;
// 對“基準標號“左右側的數值分別進行遞歸的切割,以至於將這些數值完整的排序
quickSort(arr, low, s_low - 1);
quickSort(arr, s_low + 1, hi);
}
}
堆排序
桶排序
主要思想:桶排序是將待排序集合中處於同一個值域的元素存入同一個桶中,也就是根據元素值特性將集合拆分爲多個區域,則拆分後形成的多個桶,從值域上看是處於有序狀態的。對每個桶中元素進行排序,則所有桶中元素構成的集合是已排序的。
二分查找
///二分查找、折半查找,非遞歸實現
template <typename T>
T Binary_Search(T *x, int N, T keyword) {
int low = 0, hi = N - 1,mid;
while (low <= hi) {
mid = low + (hi - low) >> 1;
if (x[mid] == keyword)
return mid;
if (x[mid] < keyword)
low = mid+1;
if (x[mid] > keyword)
hi = mid - 1;
if (x[mid] < keyword)
low = mid + 1;
}
return -1;
}
///非遞歸實現
int Binary_Search2(int *a, int low, int hi, int key) {
if (low > hi)
return -1;
int mid = low + (hi - low) >> 1;
if (a[mid] == key)
return mid;
if (a[mid] > key)
Binary_Search2(a, low, mid - 1, key);
else
Binary_Search2(a, mid + 1, hi, key);
}
哈希表查找
二叉排序查找
未完.....