<span style="font-family: 'Microsoft YaHei'; background-color: rgb(255, 255, 255);">學習了排序算法之後,覺得應該把它總結一遍,順一遍自己的思路。因爲排序算法比較雜,所以下來和大家一起來看看這麼多的排序算法。先來看</span><span style="font-family: 'Microsoft YaHei'; background-color: rgb(255, 255, 255);">比較排序,</span>
我們看下面一張圖
我們就以下面的圖來講一講各種排序,
1.直接插入排序
思想:
每次選擇一個元素K,將K插入到已經有序的部分A[1....i]中,插入之前,要把K和A中的每個元素進行比較,若K<A[n],則將K插入到A[n]之前(n是A中的某個下標),A[n]之後的所有元素都要向後移動,若發現A[i]>K,則K插到A[i]的後面。
時間複雜度:
最好的情況:正向有序,只需要比較n 次,不需要移動,時間複雜度O(N);
最壞的情況:逆序有序,每一個元素都要移動接近n次,時間複雜度O(N²);
平均時間複雜度:O(N²)
穩定性:
穩定性就是兩個相同的元素排序完之後若兩者順序不變,就是穩定性高,反之穩定性低。
K1是有序部分的元素,插入K2,若K2=K1,則把K2插到K1後面即可,K1不需要移動,所以插入排序穩定性較高。
代碼:
<span style="font-family:Times New Roman;font-size:18px;">void InsertSort(int* a, size_t n)
{
int i = 0;
for (i = 0; i < n - 1; i++)
{
int end = i;
int tmp = end + 1;
while ((end >= 0) && (a[end]>a[tmp]))
{
swap(a[end], a[tmp]);
end--;
tmp--;
}
}
}</span>
2.希爾排序
思想:
希爾排序也是插入排序的一種,實際上是一種分組插入的方法,這樣做的目的是爲了讓大的數據儘快的跑到前面去,先確定一個小於size的分量gap,將所有距離爲gap的元素分爲一組進行插入排序,然後取第二個分量gap(小於原來的gap)繼續上一步操作,直到gap取到1,也就是最後的一次直接插入排序。
時間複雜度:
最壞情況下:小於插入排序的最壞情況,大於插入排序的最好情況,大概是O(N¹·³)
平均情況:O(N¹·³)
穩定性:
我們知道一次插入排序是穩定的,但是多次插入排序中,相同的元素可能會在各自的分組中進行移動,就有可能會打亂相同元素原本的順序,所以希爾排序是不穩定的
代碼:
<span style="font-size:18px;">void ShellSort(int* a, size_t n)
{
int gap = n;
int i = 0;
while (gap > 1)
{
gap = gap / 3 + 1;//保證最後一次是直接插入排序
for (i = 0; i< n - gap; i++)
{
int end = i;
int tmp = a[end + gap];
while ((a[end] > tmp) && (end >= 0))
{
//swap(a[end + gap], a[end]);
a[end + gap] = a[end];
end -= gap;
}
a[end + gap] = tmp;
}
}
}</span>
3.冒泡排序
思想:
通過無序區間中相鄰字段的比較,將一個最大的數冒到區間的尾部,然後縮小空間,再從頭開始比較,每一冒一個當前空間中最大的數,直到區間長度爲1.
時間複雜度:
最好情況:正向有序,只需要比較n次,O(N)
最壞情況:逆向有序,每個數字都要比較n次,並且移動n次,O(N²)
平均情況:O(N²)
穩定性:
排序中只會交換兩個相鄰的元素,因此嗎,當兩個相鄰元素相同時,是沒有必要進行交換的,所以冒泡排序是穩定的
代碼:
<span style="font-family:Microsoft YaHei;">void BubbleSort(int* a, size_t n)
{
int end = n - 1;
while (end)
{
for (int i = 0; i <= end; i++)
{
if (a[i - 1] > a[i])
{
swap(a[i - 1], a[i]);
}
}
end--;
}
}
</span>
4.選擇排序
思想:
在未排序區間中找到最大元素,放至區間末尾,再到剩下的元素中找最大元素,放至末尾,直到未排序區間只剩下一個元素
時間複雜度:
最好情況:正向有序,只需要比較,不需要交換,O(N²)
最壞情況:逆向有序,比較並且需要交換嗎,也是O(N²)
平均情況:O(N²)
穩定性:
排序中可能會交換最後一個元素的位置,最後一個元素的位置很不確定,所以不穩定
代碼;
void SelectSort(int* a, size_t n)
{
int start = 0;
int end = n - 1;
while (start < end)
{
int MaxIndex = start;
int MinIndex = start;
for (int i = start + 1; i <= end; i++)
{
if (a[i] >= a[MaxIndex])
{
MaxIndex = i;
}
if (a[i] <= a[MinIndex])
{
MinIndex = i;
}
}
swap(a[start], a[MinIndex]);
//排除最大的在第一位,最小的在最後一位要交換兩次的情況
if (a[end] < a[MaxIndex])
{
swap(a[end], a[MaxIndex]);
}
start++;
end--;
}
}
思想:
-
從數列中挑出一個元素作爲基準數。
-
分區過程,將比基準數大的放到右邊,小於或等於它的數都放到左邊。
-
再對左右區間遞歸執行第二步,直至各區間只有一個數。
最好情況下:每次劃分軸值的左側子序列與右側子序列的長度相同,時間複雜度爲O(nlogn),
最壞情況下:待排序序列爲正序或逆序,時間複雜度爲O(n^2);
平均情況下:時間複雜度爲O(nlogn)
代碼;
int GetKey(int* a, int left, int right)
{
int key = a[right];
int start = left;
int end = right;
while (start < end)
{
while ((a[start]<key) && (start<end))
{
start++;
}
if (a[start]>key)
{
a[end] = a[start];
end--;
}
while ((a[end]>key) && (start<end))
{
end--;
}
if (a[end] < key)
{
a[start] = a[end];
start++;
}
}
a[start] = key;
return start;
}
// 非遞歸
void QuickSort(int* a, int left, int right)
{
stack<int> s;
s.push(right);
s.push(left);
while (!s.empty())
{
int start = s.top();
s.pop();
int end = s.top();
s.pop();
if (start < end)
{
int div = GetKey(a, start, end);
s.push(end);
s.push(div + 1);
s.push(div - 1);
s.push(start);
}
}
}
遞歸:
left,right]
void QuickSort(int* a,int left, int right)
{
if (left < right)
{
int pos = GetKey(a, left, right);
QuickSort(a, left, pos-1);
QuickSort(a, pos + 1, right);
}
}
6.歸併排序
思想;將若干個有序序列進行兩兩歸併,直至所有待排記錄都在一個有序序列爲止。
先考慮合併兩個有序數組,基本思路是比較兩個數組的最前面的數,誰小就先取誰,取了後相應的指針就往後移一位。
然後再比較,直至一個數組爲空,最後把另一個數組的剩餘部分複製過來即可。
再考慮遞歸分解,基本思路是將數組分解成 left 和 right,如果這兩個數組內部數據是有序的,
那麼就可以用上面合併數組的 方法將這兩個數組合並排序。如何讓這兩個數組內部是有序的?可以再二分,直至分解出的小組只含有一個元素時爲止,此時認爲該小組內部已有序。然後合併排序相鄰二個小組即可。
時間複雜度:
最好,最壞,平均都是O(nlogn)。
代碼:
oid Merge(int* a, int* tmp, int start, int mid, int end)
{
int i = start;
int j = mid + 1;
int k = start;
while ((i < mid + 1) && (j < end + 1))
{
while ((a[i] <= a[j]) && (i < mid + 1))
{
tmp[k++] = a[i++];
}
while ((a[i] > a[j]) && (j < end + 1))
{
tmp[k++] = a[j++];
}
}
while (i < mid + 1)
{
tmp[k++] = a[i++];
}
while (j < end + 1)
{
tmp[k++] = a[j++];
}
for ( i = start; i < k; i++)
{
a[i] = tmp[i];
}
}
void MergeSort(int* a, int* tmp, int start, int end)
{
if (start < end)
{
int mid = (start + end) / 2;
MergeSort(a, tmp, start, mid);
MergeSort(a, tmp, mid + 1, end);
Merge(a, tmp, start, mid, end);
}
}
7,堆排
思想:
1,堆排序是對簡單選擇排序的改進。
2,首先將待排序的記錄序列構造成一個堆,此時,選出了堆中所有記錄的最大者即堆頂記錄,然後將他從堆中移走,並將剩下的記錄再調整成堆,這樣又找出了次大的記錄,以此類推,直到堆中只有一個記錄爲止。
時間複雜度;
最好,最壞,平均的時間複雜度都是O(nlogn)
代碼:
void _AdjustDown(int* a, size_t n, int i)
{
int parent = i;
int child = 2 * i + 1;
while (child < n)
{
if((child + 1 < n) && (a[child] < a[child + 1]))
{
child++;
//swap(a[child], a[child + 1]);
}
if (a[child] > a[parent])
{
swap(a[child], a[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, size_t n)
{
int i = 0;
for (i = (n - 2) / 2; i >= 0; i--)
{
_AdjustDown(a, n, i);
}
int size = n;
for (i = 0; i < n; i++)
{
swap(a[0], a[n - 1 - i]);
_AdjustDown(a, --size, 0);
}
}
就講到這了,小夥伴們聽懂了嗎