不稳定算法记忆口诀 “快些选队”
区分标准
排序前后两个相等的数相对位置不变,则算法稳定;
排序前后两个相等的数相对位置发生了变化,则算法不稳定;
一、冒泡排序
void bubbleSort(int a[], int n) {
int i,j,temp;
for (j = 0; j < n-1; j++){
for (i=0; i<n-1-j; i++){
if(a[i] > a[i+1]){
temp = a[i];
a[i] = a[i+1];
a[i+1] = temp;
}
}
}
}
二、快速排序
void quikSort(int a[], int low,int high){
int i = low;
int j = high;
int temp = a[i];
if(low < high){
while(i < j) {
while((a[j] >= temp) && (i < j)){
j--;
}
a[i] = a[j];
while((a[i] <= temp) && (i < j)){
i++;
}
a[j]= a[i];
}
a[i] = temp;
quiksort(a,low,i-1);
quiksort(a,j+1,high);
}
}
不稳定实例 {2, 1, 3, 3, 1, 1, 1}
时间复杂度分析
快速排序平均每次把问题划分两个子问题,其递归关系式可表示如下
T
(
n
)
=
2
T
(
n
2
)
+
O
(
n
)
T(n) = 2T({n\over 2}) + O(n)
T(n)=2T(2n)+O(n)
其中
O
(
n
)
O(n)
O(n)为一次划分的时间复杂度,对比主定理
a
=
2
a = 2
a=2,
b
=
2
b=2
b=2,
n
l
o
g
b
a
=
n
l
o
g
2
2
=
n
n^{log^{a}_{b}}=n^{log^{2}_{2}}=n
nlogba=nlog22=n,符合情况2,所以
时
间
复
杂
度
O
(
n
l
o
g
b
a
l
o
g
n
)
=
O
(
n
l
o
g
n
)
时间复杂度O(n^{log^{a}_{b}}logn)=O(nlogn)
时间复杂度O(nlogbalogn)=O(nlogn)。
在极端的情况下,每次只能划分成一个子问题,一边一个,另外一边
n
−
1
n-1
n−1个,递推关系式退化成如下
T
(
n
)
=
T
(
n
−
1
)
+
T
(
1
)
+
O
(
n
)
T(n)=T(n-1)+T(1)+O(n)
T(n)=T(n−1)+T(1)+O(n)
此时,时间复杂度为
O
(
n
2
)
O(n^{2})
O(n2)。
应用
- 利用快排思想寻找数组中第K大(小)的数字;
- 求数组中出现次数超过一半的数(时间复杂度O(n));
三、选择排序
void selectionSort(int *a,int n){
int i, j, temp;
for(i = 0; i < n-1; i++){
for(j = i+1; j < n; j++){
if(a[i] > a[j]){
temp = a[i];
a[i] =a [j];
a[j] = temp;
}
}
}
不稳定实例 {5, 8, 5, 2, 9}
四、插入排序
void insertionSort(int a[], int n) {
int i, key, j;
for (i = 1; i < n; i++) {
key = a[i];
j = i - 1;
/* Move elements of a[0..i-1], that are
greater than key, to one position ahead
of their current position */
while (j >= 0 && a[j] > key) {
a[j + 1] = a[j];
j = j - 1;
}
a[j + 1] = key;
}
}
五、归并排序
void merge(int arr[], int l, int m, int r) {
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;
int L[n1], R[n2];
for (i = 0; i < n1; i++) {
L[i] = arr[l + i];
}
for (j = 0; j < n2; j++){
R[j] = arr[m + 1 + j];
}
i = 0, j = 0, k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
void mergeSort(int arr[], int l, int r) {
if (l < r) {
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
}
}
时间复杂度分析
归并排序每次把问题划分为两个规模相等的子问题,子问题排好序后再合并。合并函数merge
时间复杂度为
O
(
n
)
O(n)
O(n),所以归并排序递归表达式如下
T
(
n
)
=
2
T
(
n
2
)
+
O
(
n
)
T(n) = 2T({n \over 2 }) + O(n)
T(n)=2T(2n)+O(n)
根据主定理可以计算该时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
因为不管元素在什么情况下都要做这些步骤,所以该算法最优时间复杂度和最差时间复杂度及平均时间复杂度都是一样的。
六、堆排序
void heapify(int arr[], int n, int i) {
int largest = i; // 根
int l = 2*i + 1; // 左
int r = 2*i + 2; // 右
// 左大于根
if (l < n && arr[l] > arr[largest])
largest = l;
// 右大于根
if (r < n && arr[r] > arr[largest])
largest = r;
if (largest != i) {
swap(arr[i], arr[largest]);
// 递归
heapify(arr, n, largest);
}
}
void heapSort(int arr[], int n) {
// Build heap
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
for (int i=n-1; i>0; i--) {
// Move current root to end
swap(arr[0], arr[i]);
heapify(arr, i, 0);
}
}
时间复杂度:
O
(
n
)
+
(
n
−
1
)
∗
O
(
l
g
n
)
=
O
(
n
l
g
n
)
O(n) + (n-1)*O(lgn)=O(nlgn)
O(n)+(n−1)∗O(lgn)=O(nlgn)
其中
- O ( n ) O(n) O(n): 建堆时间复杂度
- ( n − 1 ) O ( l g n ) (n-1)O(lgn) (n−1)O(lgn) : 每次将堆顶和最后一个元素交换后,调整堆时间复杂度为 O ( l g n ) O(lgn) O(lgn),交换 n − 1 n-1 n−1次
因为每次都要把根节点移到最后,所以假定数组值是一样的,很显然原来的顺序不能维持;