寫在前面
排序算法很容易被忽視,因爲sort的存在,排序算法不管是在面試還是競賽,都是很關鍵的算法,所以今天整理一下。
冒泡排序
重複地走訪過要排序的數列, 一次比較兩個元素, 如果他們的順序錯誤就把他們交換過來。 走訪數列的工作是重複地進行直到沒有再需要交換, 也就是說該數列已經排序完成。
void bubble_sort(int arry[], int left, int right) {
for(int i = left; i < right; i++) {
for(int j = i; j < right; j++) {
if(arry[j] > arry[j + 1]) {
swap(arry[j], arry[j + 1]);
}
}
}
}
時間複雜度:O(n^2),
空間複雜度:O(n)
選擇排序
首先在未排序序列中找到最小( 大) 元素, 存放到排序序列的起始位置, 然後, 再從剩餘未排序元素中繼續尋找最小( 大) 元素, 然後放到已排序序列的末尾。 以此類推, 直到所有元素均排序完畢。
void select_sort(int arry[], int left, int right) {
for(int i = left; i < right; i++) {
int Min = i;
for(int j = i + 1; j <= right; j++) {
if(arry[Min] > arry[j]) {
Min = j;//很多選擇排序這裏是交換,其實不需要,記錄位置就行了。
}
}
if(Min != i) {//把最小(大)的數換到前面。
swap(arry[i], arry[Min]);
}
}
}
時間複雜度:O(n^2),
空間複雜度:O(n)
直接插入排序
插入排序( Insertion Sort) 的算法描述是一種簡單直觀的排序算法。 它的工作原理是通過構建有序序列, 對於未排序數據, 在已排序序列中從後向前掃描, 找到相應位置並插入。 插入排序在實現上, 通常採用inplace排序( 即只需用到O(1)的額外空間的排序) , 因而在從後向前掃描過程中, 需要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。
void insert_sort(int arry[], int left, int right) {
for(int i = left + 1; i <= right; i++) {
int tmp = arry[i];
int j = i - 1;
while(j >= left && arry[j] > tmp) {
arry[j + 1] = arry[j];
j--;
}
arry[j + 1] = tmp;
}
}
時間複雜度:O(n^2),
空間複雜度:O(n)
快速排序
快排是面試最喜歡問的算法,基本思想其實就是二分和遞歸。
從序列中選取一個作爲關鍵字, 對序列排一次序, 使得關鍵字左側的數都比關鍵字小, 右側的都大於等於關鍵字( 左右兩側的序列依然是無序的) , 然後 將左側的序列按照同樣的方法進行排序, 將右側序列也按照同樣的方法排序, 已達到整個序列有序。
詳解的傳送門:快速排序
void quick_sort(int arry[], int left, int right) {
if(left >= right) return ;
int i = left, j = right;
int pivot = arry[left];//這裏選取的是最左邊的數爲pivot,如果想減少數據有序性對算法速度的影響,可以換成隨機。
while (i < j)
{
while (arry[j] >= pivot && i < j)
j--;
while (arry[i] <= pivot && i < j)
i++;
if(i < j)
{
temp = arry[i];
arry[i] = arry[j];
arry[j] = temp;
}
}
arry[left] = arry[i];
arry[i] = pivot;
quick_sort(arry, left, i - 1);
quick_sort(arry, i + 1, right);
}
時間複雜度:O(nlog(n)),
空間複雜度:O(n)
堆排序
堆排序( Heapsort) 是指利用堆這種數據結構所設計的一種排序算法。 堆積是一個近似完全二叉樹的結構, 並同時滿足堆積的性質: 即子結點的鍵值或索引總是小於( 或者大於) 它的父節點。
void Heap_just(int a[], int root, int heap_size) {
if(root < heap_size) {
int Min = root;
int l_son = root << 1 | 1;
int r_son = (root << 1) + 2;
if(l_son < heap_size && a[Min] > a[l_son]) Min =l_son;
if(r_son < heap_size && a[Min] > a[r_son]) Min =r_son;
if(Min == root) return ;
a[root] ^= a[Min];
a[Min] ^= a[root];
a[root] ^= a[Min];
Heap_just(a, Min, heap_size);
}
}
void build_heap(int a[], int n) {
for(int i = n / 2; i >= 0; i--) {
Heap_just(a, i, n);
}
}
void Heap_sort(int a[], int n) {
build_heap(a, n);//建堆
for(int i = n - 1; i > 0; i--) {
a[i] ^= a[0];
a[0] ^= a[i];
a[i] ^= a[0];
Heap_just(a, 0, i);
}
}
時間複雜度:O(nlog(n))
桶排序
桶排序 (Bucket sort)或所謂的箱排序, 是一個排序算法, 工作的原理是將數組分到有限數量的桶子裏。 每個桶子再個別排序( 有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排序) 。 桶排序是穩定的, 且在大多數情況下常見排序裏最快的一種,比快排還要快, 缺點是非常耗空間,基本上是最耗空間的一種排序算
法, 而且只能在某些情形下使用。
時間複雜度:O(n)
歸併排序
歸併排序是一種穩定的算法, 採用分治的思想, 有序的子序列合併得到有序序列。
實現步驟:
- 將序列分成長度爲 n/2的兩部分
- 對於左右兩部分採用分治的方法得到有序序列
- 將左右兩個有序序列合併得到整個有序序列
void arry_add(int arry[], int left, int mid, int right) {
if(left >= right) return ;
int i = left, j = mid + 1, k = 0;
while(i <= mid && j <= right) {
if(arry[i] <= arry[j]) {
tmp[k++] = arry[i++];
}
else {
tmp[k++] = arry[j++];
cnt += (mid - i + 1);
}
}
while(i <= mid) {
tmp[k++] = arry[i++];
}
while(j <= right) {
tmp[k++] = arry[j++];
}
for(i = 0; i < k; i++) {
arry[i + left] = tmp[i];
}
}
void merge_sort(int arry[], int left, int right) {
if(left >= right) return ;
int mid = (left + right) >> 1;
merge_sort(arry, left, mid);
merge_sort(arry, mid + 1, right);
arry_add(arry, left, mid, right);
}
時間複雜度:O(nlog(n))
二分插入排序
二分( 折半) 插入( Binary insert sort)排序是一種在直接插入排序算法上進行小改動的排序算法。 其與直接排序算法最大的區別在於查找插入位置時使用的是二分查找的方式, 在速度上有一定提升。
void Binary_Insert_sort(int arry[], int first, int end) {
for(int i = first + 1; i <= end; i++) {
int low = first, high = i - 1;
while(low <= high) {
int mid = (low + high) >> 1;
if(arry[mid] > arry[i]) {
high = mid - 1;
}
else {
low = mid + 1;
}
}
int key = arry[i];
for(int j = i; j > high + 1; j--) {
arry[j] = arry[j - 1];
}
arry[high + 1] = key;
}
}
希爾排序
希爾排序, 也稱遞減增量排序算法, 因DL. Shell於1959年提出而得名, 是插入排序的一種高速而穩定的改進版本。
實現步驟:
- 先取一個小於n的整數d1作爲第一個增量, 把文件的全部記錄分成d1個組。
- 所有距離爲d1的倍數的記錄放在同一個組中, 在各組內進行直接插入排序。
- 取第二個增量d2<d1重複上述的分組和排序。
- 直至所取的增量dt=1, 即所有記錄放在同一組中進行直接插入排序爲止。
具體詳解的傳送門:希爾排序
void shell_sort(int arry[], int left, int right) {
int n = right - left;
int gep = 1;
while(gep <= n) {
gep = gep << 1 | 1;//最大增量
}
while(gep >= 1) {
for(int i = left + gep; i <= right; i++) {
int temp = arry[i];
int j = i - gep;
while(j >= left && arry[j] > temp) {
arry[j + gep] = arry[j];
j -= gep;
}
arry[j + gep] = temp;
}
gep = (gep - 1) / 2;//增量減少
}
}
時間複雜度:小於O(n^2)
雞尾酒排序
雞尾酒排序等於是冒泡排序的輕微變形。 不同的地方在於從低到高然後從高到低,而冒泡排序則僅從低到高去比較序列裏的每個元素。 他可以得到比冒泡排序稍微好一點的效能, 原因是冒泡排序只從一個方向進行比對(由低到高), 每次循環只移動一個項目。
void cocktail_sort(int arry[], int left, int right) {
int i = left, j = right;
bool isSwap = false;
do {
for(int k = i; k < j; k++) {
if(arry[k] > arry[k + 1]) {
swap(arry[k], arry[k + 1]);
}
}
isSwap = false;
j--;
for(int k = j; k > i; k--) {
if(arry[k] < arry[k - 1]) {
swap(arry[k], arry[k - 1]);
isSwap = true;
}
}
i++;
}while(i <= j && isSwap);
}
時間複雜度:小於O(n^2)
計數排序
計數排序(Counting sort)是一種穩定的排序算法。 計數排序使用一個額外的數組C,其中第i個元素是待排序數組A中值等於i的元素的個數。 然後根據數組C來將A中的元素排到正確的位置。 它只能對整數進行排序。
實現步驟:
- 找出待排序的數組中最大和最小的元素。
- 統計數組中每個值爲i的元素出現的次數, 存入數組C的第i項。
- 對所有的計數累加( 從C中的第一個元素開始, 每一項和前一項相加) 。
- 反向填充目標數組: 將每個元素i放在新數組的第C(i)項, 每放一個元素就將C(i)減去1 。
超詳細圖解+優化的傳送門:計數排序