演示動畫來源於:https://www.cnblogs.com/onepixel/p/7674659.html
算法分類
插入類
- 插入排序
- 希爾排序
交換類
- 冒泡排序
- 快速排序
選擇類
- 選擇排序
- 堆排序
歸併類
- 歸併排序
排序算法的性能指標:
- 時間複雜度
- 空間複雜度
- 穩定性1
具體原理以及實現
插入排序
- 算法描述:
從數組的第一個元素開始,認定該元素已經被排序,然後取下一個元素,對於已經排序的元素序列中從後往前掃描,如果已經排序的某一個元素大於新元素,則將這個元素往後移一個位置,重複此操作,直至找到已排序的某元素小於等於這個新元素,然後將這個新元素插入到該元素的後面。然後遍歷餘下的數組;
- 從第一個元素開始,認定該元素已被排序
- 取下一個元素,對於已經排序的元素序列中從後往前掃描
- 如果已經排序的某一個元素大於新元素,則將這個元素往後移一個位置
- 重複上一操作,直至找到已排序的某元素小於等於這個新元素
- 將這個新元素插入到該元素的後面
- 繼續遍歷餘下的數組
- 性能:
時間複雜度:O()
空間複雜度:O(1)
穩定性:穩定 - 代碼實現:
//插入排序
//傳進來的是數組的首地址,以及數組的長度,返回的也是數組的首地址
int* InsertSort(int *a,int lena)
{
//preindex是指有序數組的索引,會隨比較而發生變化,tmp是將要進行插入的元素
int preindex,tmp;
for (int i = 1; i < lena; i++)
{
//tmp初始化
tmp=a[i];
//初始化爲i-1
preindex = i-1;
//插入前的比較
while(preindex>=0 &&a[preindex]>tmp)
{
a[preindex+1]=a[preindex];
preindex--;
}
//preindex現在就是那個小於等於新元素的位置,插入其後
a[preindex+1]=tmp;
}
return a;
}
希爾排序
- 算法描述:
又稱爲“縮小增量排序”,屬於直接插入排序算法的一個升級版本,實現希爾排序需要手動或者動態的設置一個遞減的增量數組,這個數組最後一個數必須是1,,有了這個增量數組之後,就要對需要排序的數組b進行k次排序,每一次排序都要順序對應用到一個增量;這個增量的作用通俗一點來說就是對索引下標進行操作,如進行插入排序,進行比較…依次類推,直至無法再進行比較;然後再換下一個增量,直至結束;
-選擇或者動態一個遞減的增量序列
-進行k次排序
-每一次排序,對應一個a,分爲若干排序小組,對於這若干個排序小組進行插入排序;
-遍歷直至增量爲1
增量數組爲 5,2,1 注:此動圖旨在幫助更好的理解這個算法,實際過程中是多組交替執行的
- 性能:
時間複雜度:O()
空間複雜度:O(1)
穩定性:不穩定 - 代碼實現:
//希爾排序
//傳進來的是數組的首地址,以及數組的長度,返回的也是數組的首地址
int * ShellSort(int *a,int lena)
{
//對於增量序列,採用動態,不再手動設置
//k就是動態的增量
for (int k = lena/2; k >0; k=k/2)
{
//preindex是指有序數組的索引,會隨比較而發生變化,tmp是將要進行插入的元素
int preindex,tmp;
for (int i = k; i < lena; i++)
{
//初始化tmp
tmp=a[i];
//初始化爲i-k
preindex=i-k;
//插入前的比較
while(preindex>=0&&a[preindex]>tmp)
{
a[preindex+k]=a[preindex];
preindex-=k;
}
//preindex現在就是那個小於等於新元素的位置,插入其後
a[preindex+k]=tmp;
}
}
return a;
}
冒泡排序
- 算法描述:
這是一個比較笨但是又特別明瞭的一個算法,核心就是重複走n次,每一次遍歷,都要比較相鄰的兩個元素,如果爲逆序,那麼則把元素交換位置,否則繼續下一個比較,直至n躺走完
- 比較相鄰的兩個元素,如果爲逆序,則交換
- 然後接着遍歷,直至結束
- 重複以上步驟n次
2. 性能:
時間複雜度:O()
空間複雜度:O(1)
穩定性:穩定排序
3. 代碼演示:
//冒泡排序
//傳進來的是數組的首地址,以及數組的長度,返回的也是數組的首地址
int* BubbleSort(int *a,int lena)
{
int tmp;
for (int i = 0; i < lena-1; i++)
{
for (int j = 0; j < lena-1-i; j++)
{
if(a[j]>a[j+1])
{
tmp=a[j+1];
a[j+1]=a[j];
a[j]=tmp;
}
}
}
return a;
}
快速排序
- 算法描述:
快速排序是一個收益比較好的一個排序,其核心思想是對於每一次排序,均選擇一個樞軸(pivot),然後小於樞軸的數放在左邊,大於的數放在右邊,然後進行對左邊右邊的數進行遞歸快速排序;
- 從數組中跳出一個元素作爲樞軸
- 所有比樞軸小的元素放在樞軸左邊,大的元素則放在樞軸右邊
- 對左右均進行遞歸的快排
- 性能:
時間複雜度:O()
空間複雜度:O()
穩定性:不穩定排序 - 代碼演示:
//快速排序
//傳進來的是數組的首地址,以及開始結束的索引
void QuickSort(int *a, int left, int right)
{
//初始化,pivot是樞軸
int i = left, j = right, pivot;
//快速排序結束標誌
if (i >= j)
return;
//每一次排序開始便初始化樞軸
pivot = a[i];
while (i < j)
{
while (i < j && a[j] > pivot)
j--;
a[i] = a[j];
while (i < j && a[i] <= pivot)
i++;
a[j] = a[i];
}
a[i] = pivot;
QuickSort(a, left, i);
QuickSort(a, i + 1, right-1);
}
選擇排序
- 算法描述:
這是一個簡單直白的排序方式,很切合一般人的思維,核心思想就是進行n-1次排序,每一次排序都選擇一個最小值,然後將選好的最小值放入有序區(從索引0開始),然後開始對無序區接着排序,有序無序區均用同一個數組。
-初始化,有序區爲空,無序區爲a[0]…a[n-1]
-第i次排序,從a[i-1]開始一直到末尾,選出最小的一個,然後與a[i-1]互換
-重複n-1次
- 性能
時間複雜度:O()
空間複雜度:O(1)
穩定性:不穩定排序 - 代碼演示:
//選擇排序
//傳進來的是數組的首地址,以及數組的長度,返回的也是數組的首地址
int* SelectSort(int *a,int lena)
{
int minindex,tmp;
for (int i = 0; i < lena-1; i++)
{
minindex=i;
for (int j = i+1; j < lena; j++)
{
if(a[j]<a[minindex])
minindex=j;
}
tmp=a[i];
a[i]=a[minindex];
a[minindex]=tmp;
}
return a;
}
堆排序
- 算法描述
堆排序是利用了堆這種數據結構,因爲堆本身就要滿足堆積的性質:孩子結點的鍵值小於其父節點(本博客中使用這個性質)。那麼就先初始化一個這樣的大頂堆,,將次堆分爲兩部分,一部分是無序的大頂堆,另一部分就是有序的排列數組,然後將堆頂元素與無序的大頂堆最後一個元素交換,交換後的無序堆的最後一個歸爲有序數組;然後重複此操作;
-初始化大頂堆
-將堆頂的元素與無序區的最後一個元素交換
-交換後的無序區的最後一個元素,歸爲有序區
-重複n次
- 性質:
時間複雜度:O()
空間複雜度:O(n)
穩定性:不穩定排序 - 代碼演示:
//堆排序,代碼來源於https://www.cnblogs.com/skywang12345/p/3602162.html#a42
//輔助函數,(最大)堆的向下調整算法
void maxHeapDown(int *a, int start, int end)
{
int c = start; // 當前(current)節點的位置
int l = 2 * c + 1; // 左(left)孩子的位置
int tmp = a[c]; // 當前(current)節點的大小
for (; l <= end; c = l, l = 2 * l + 1)
{
// "l"是左孩子,"l+1"是右孩子
if (l < end && a[l] < a[l + 1])
l++; // 左右兩孩子中選擇較大者,即m_heap[l+1]
if (tmp >= a[l])
break; // 調整結束
else // 交換值
{
a[c] = a[l];
a[l] = tmp;
}
}
}
//傳進來的是數組的首地址,以及數組的長度
void HeapSort(int *a, int n)
{
int i, tmp;
// 從(n/2-1) --> 0逐次遍歷。遍歷之後,得到的數組實際上是一個(最大)二叉堆。
for (i = n / 2 - 1; i >= 0; i--)
maxHeapDown(a, i, n - 1);
// 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
for (i = n - 1; i > 0; i--)
{
// 交換a[0]和a[i]。交換後,a[i]是a[0...i]中最大的。
tmp = a[0];
a[0] = a[i];
a[i] = tmp;
// 調整a[0...i-1],使得a[0...i-1]仍然是一個最大堆。
// 即,保證a[i-1]是a[0...i-1]中的最大值。
maxHeapDown(a, 0, i - 1);
}
}
歸併排序
- 算法描述:
歸併排序是建立在歸併操作上的一種有效的排序方法;其總共有兩個步驟,一是分割,二是集成;對於一個無序的序列,要遞歸的把當前序列平均分割爲兩半,然後對這個子序列在保持元素順序的同時進行集成(歸併)操作,最後將兩個排序好的子序列合併爲一個最終的排序的序列;
-把一個無序的序列平均分爲兩部分
-對於兩個子序列分別採用歸併排序(實質上就是遞歸)
-將兩個排序的子序列合併爲一個最終的排序的序列; - 性能:
時間複雜度:O()
空間複雜度:O(n)
穩定性:不穩定排序 - 代碼演示:
//歸併排序,代碼來源於:https://blog.csdn.net/zpznba/article/details/83745205
//歸併排序輔助函數
void Merge(int* arr, int l, int q, int r)
{
int n = r - l + 1; //臨時數組存合併後的有序序列
int *tmp = new int[n];
int i = 0;
int left = l;
int right = q + 1;
while (left <= q && right <= r)
tmp[i++] = arr[left] <= arr[right] ? arr[left++] : arr[right++];
while (left <= q)
tmp[i++] = arr[left++];
while (right <= r)
tmp[i++] = arr[right++];
for (int j = 0; j < n; ++j)
arr[l + j] = tmp[j];
delete[] tmp; //刪掉堆區的內存
}
//傳進來的是數組的首地址,以及l是左索引,r是右索引
void MergeSort(int* arr, int l, int r)
{
if (l == r)
return; //遞歸基是讓數組中的每個數單獨成爲長度爲1的區間
int q = (l + r) / 2;
MergeSort(arr, l, q);
MergeSort(arr, q + 1, r);
Merge(arr, l, q, r);
}
總和代碼
#include <iostream>
using namespace std;
struct SortFunction
{
//插入排序
//傳進來的是數組的首地址,以及數組的長度
void InsertSort(int *a, int lena)
{
//preindex是指有序數組的索引,會隨比較而發生變化,tmp是將要進行插入的元素
int preindex, tmp;
for (int i = 1; i < lena; i++)
{
//tmp初始化
tmp = a[i];
//初始化爲i-1
preindex = i - 1;
//插入前的比較
while (preindex >= 0 && a[preindex] > tmp)
{
a[preindex + 1] = a[preindex];
preindex--;
}
//preindex現在就是那個小於等於新元素的位置,插入其後
a[preindex + 1] = tmp;
}
}
//希爾排序
//傳進來的是數組的首地址,以及數組的長度
void ShellSort(int *a, int lena)
{
//對於增量序列,採用動態,不再手動設置
//k就是動態的增量
for (int k = lena / 2; k > 0; k = k / 2)
{
//preindex是指有序數組的索引,會隨比較而發生變化,tmp是將要進行插入的元素
int preindex, tmp;
for (int i = k; i < lena; i++)
{
//初始化tmp
tmp = a[i];
//初始化爲i-k
preindex = i - k;
//插入前的比較
while (preindex >= 0 && a[preindex] > tmp)
{
a[preindex + k] = a[preindex];
preindex -= k;
}
//preindex現在就是那個小於等於新元素的位置,插入其後
a[preindex + k] = tmp;
}
}
}
//冒泡排序
//傳進來的是數組的首地址,以及數組的長度
void BubbleSort(int *a, int lena)
{
int tmp;
for (int i = 0; i < lena - 1; i++)
{
for (int j = 0; j < lena - 1 - i; j++)
{
if (a[j] > a[j + 1])
{
tmp = a[j + 1];
a[j + 1] = a[j];
a[j] = tmp;
}
}
}
}
//快速排序
//傳進來的是數組的首地址,以及開始結束的索引
void QuickSort(int *a, int left, int right)
{
//初始化,pivot是樞軸
int i = left, j = right, pivot;
//快速排序結束標誌
if (i >= j)
return;
//每一次排序開始便初始化樞軸
pivot = a[i];
while (i < j)
{
while (i < j && a[j] > pivot)
j--;
a[i] = a[j];
while (i < j && a[i] <= pivot)
i++;
a[j] = a[i];
}
a[i] = pivot;
QuickSort(a, left, i);
QuickSort(a, i + 1, right - 1);
}
//選擇排序
//傳進來的是數組的首地址,以及數組的長度
void SelectSort(int *a, int lena)
{
int minindex, tmp;
for (int i = 0; i < lena - 1; i++)
{
minindex = i;
for (int j = i + 1; j < lena; j++)
{
if (a[j] < a[minindex])
minindex = j;
}
tmp = a[i];
a[i] = a[minindex];
a[minindex] = tmp;
}
}
//堆排序,代碼來源於https://www.cnblogs.com/skywang12345/p/3602162.html#a42
//輔助函數,(最大)堆的向下調整算法
void maxHeapDown(int *a, int start, int end)
{
int c = start; // 當前(current)節點的位置
int l = 2 * c + 1; // 左(left)孩子的位置
int tmp = a[c]; // 當前(current)節點的大小
for (; l <= end; c = l, l = 2 * l + 1)
{
// "l"是左孩子,"l+1"是右孩子
if (l < end && a[l] < a[l + 1])
l++; // 左右兩孩子中選擇較大者,即m_heap[l+1]
if (tmp >= a[l])
break; // 調整結束
else // 交換值
{
a[c] = a[l];
a[l] = tmp;
}
}
}
//傳進來的是數組的首地址,以及數組的長度
void HeapSort(int *a, int n)
{
int i, tmp;
// 從(n/2-1) --> 0逐次遍歷。遍歷之後,得到的數組實際上是一個(最大)二叉堆。
for (i = n / 2 - 1; i >= 0; i--)
maxHeapDown(a, i, n - 1);
// 從最後一個元素開始對序列進行調整,不斷的縮小調整的範圍直到第一個元素
for (i = n - 1; i > 0; i--)
{
// 交換a[0]和a[i]。交換後,a[i]是a[0...i]中最大的。
tmp = a[0];
a[0] = a[i];
a[i] = tmp;
// 調整a[0...i-1],使得a[0...i-1]仍然是一個最大堆。
// 即,保證a[i-1]是a[0...i-1]中的最大值。
maxHeapDown(a, 0, i - 1);
}
}
//歸併排序,代碼來源於:https://blog.csdn.net/zpznba/article/details/83745205
//歸併排序輔助函數
void Merge(int* arr, int l, int q, int r)
{
int n = r - l + 1; //臨時數組存合併後的有序序列
int *tmp = new int[n];
int i = 0;
int left = l;
int right = q + 1;
while (left <= q && right <= r)
tmp[i++] = arr[left] <= arr[right] ? arr[left++] : arr[right++];
while (left <= q)
tmp[i++] = arr[left++];
while (right <= r)
tmp[i++] = arr[right++];
for (int j = 0; j < n; ++j)
arr[l + j] = tmp[j];
delete[] tmp; //刪掉堆區的內存
}
//傳進來的是數組的首地址,以及l是左索引,r是右索引
void MergeSort(int* arr, int l, int r)
{
if (l == r)
return; //遞歸基是讓數組中的每個數單獨成爲長度爲1的區間
int q = (l + r) / 2;
MergeSort(arr, l, q);
MergeSort(arr, q + 1, r);
Merge(arr, l, q, r);
}
} Sort;
int main()
{
int a[5] = {5, 6, 2, 6, 1};
Sort.MergeSort(a,0,4);
for (int i = 0; i < 5; i++)
{
cout << a[i] << endl;
}
return 0;
}
創作不易,點個贊支持一下唄!
該處的穩定性是指遇到相同內容的元素後,排序之後有關相同元素的順序是否發生改變;例如原數據元素序列爲:,我們可以發現有兩個5,而經過某排序後,序列爲:的爲穩定排序,否則爲不穩定排序 ↩︎