插入排序
- 思路:从第二个元素开始,将之前的元素分别于当前进行比较,如果比当前元素大,那就将当这个元素向后移动一个位置。重复执行n-1趟。
- 时间复杂度:O(
)。当待排序序列逆序时,时间复杂度最差:每趟比较并移动i(1~n-1)个元素,进行n-1趟,1+2+…+n-1=n(n-1)/2。当待排序序列基本有序时,时间复杂度最小为O(n):每趟只需要比较1次,共进行n-1趟,1+1+…+1=n-1。
- 空间复杂度:O(1)。插入排序是在原有数组的基础上进行的,排序所需要新增的空间与待排序序列的元素个数无关。
- 代码实现:
#include<stdio.h> int main() { int i,j, t, arr[10] = {21,45,76,68,54,89,94,14,37,5}; for (i = 1; i < 10; i++) { t = arr[i]; j = i - 1; while (j >= 0 && arr[j] > t) { arr[j + 1] = arr[j--]; } arr[j + 1] =t; } for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
折半插入排序
- 思路:就是将普通插入排序的查找过程换成折半查找,因此折半插入排序相对于普通插入排序减少了元素之间的比较次数。
- 平均时间复杂度:O(
).
- 空间复杂度:O(1)。
- 代码实现:
#include<stdio.h> void bin_intsertSort(int arr[], int n) { int i, j, mid, high, low,t; for (i = 1; i < n; i++) { t = arr[i]; low = 0; high = i - 1; while (low <= high) { mid = (low + high) / 2; if (arr[mid] > t) { high = mid - 1; //high是需要移动的元素前一个位置 } else { low = mid+1; //low是需要移动元素的最靠前的元素 } } for (j = i - 1; j >= low; j--) { arr[j + 1] = arr[j]; } arr[j + 1] = t; } } int main() { int i,j, t, arr[10] = {21,45,76,68,54,89,94,14,37,5}; bin_intsertSort(arr, 10); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
希尔排序
- 思路:在普通插入排序的基础上,增加了间隔循环。将少了逆序对的存在,相对于普通插入排序减少了元素之间的交换次数。
- 时间复杂度:介于O(
)与O(
)之间。
- 空间复杂度:O(1)。
- 代码实现:
#include<stdio.h> void shellSort(int arr[], int n) { int i, j, gap, t; for (gap = n / 2; gap >= 1; gap /= 2) { //在最外层增加了间隔 for (i = 1 + gap; i < n; i++) { t = arr[i]; for (j = i - gap; j >= 0 && arr[j] > t; j -= gap) { arr[j + gap] = arr[j]; } arr[j + gap] = t; } } } int main() { int i, arr[10] = {21,45,76,68,54,89,94,14,37,5}; shellSort(arr, 10); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
冒泡排序
- 思路:每趟将从第一个元素开始,将当前元素与它后面的一个元素进行比较,比后面的元素大时,则交换位置。这样一趟排序结束后,待排序元素就在待排序序列的最后一个位置了,然后将这个元素从待排序序列中剔除,继续将待排序序列进行排序。冒泡排序中,当一趟排序结束后没有交换位置时,则排序结束。
- 时间复杂度:O(
)。
- 空间复杂度:O(1)。
- 代码实现:
#include<stdio.h> void bubbleSort(int arr[], int n) { int i, j, flag=1, t; for (i = n-2; i>=0 && flag == 1; i--) { //i是每趟需要向后冒泡的最后一个元素下标 flag = 0; for (j = 0; j <= i; j++) { if (arr[j] > arr[j + 1]) { flag = 1; t = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = t; } } } } int main() { int i, arr[10] = {21,45,76,68,54,89,94,14,37,5}; bubbleSort(arr, 10); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
快速排序
- 思想:用第一个元素把待排序序列划分成左边的元素都小于它,右边的元素都大于它。
- 时间复杂度:最好情况的时间复杂度为
。当待排序序列基本有序时,会将原序列划分成不平衡的两部分,时间复杂度最坏为
。
- 空间复杂度:空间复杂度就是递归的层数。平均空间复杂度为
。
- 代码实现:
#include<stdio.h> int partition(int arr[], int low, int high) { int t = arr[low]; while (low < high) { while (low < high&&arr[high] >= t)--high; arr[low] = arr[high]; while (low < high&&arr[low] <= t)++low; arr[high] = arr[low]; } arr[low] = t; return low; } void quickSort(int arr[], int low, int high) { if (low >= high) { return; } int mid = partition(arr, low, high); quickSort(arr, low, mid - 1); quickSort(arr, mid + 1, high); } int main() { int i, arr[10] = {21,45,76,68,54,89,94,14,37,5}; quickSort(arr, 0, 9); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
快速排序(非递归实现)
- 思想:用栈来模拟递归,每次循环相当于调用一次函数。
- 时间复杂度:同上。
- 空间复杂度:同上。
- 代码实现:
#include<stdio.h> int stack[100], top = -1; void quickSort(int arr[], int low, int high) { int l, h ,t,mid; stack[++top] = high; stack[++top] = low; while (top != -1) { low=l = stack[top--]; //low、high相当于每次的递归的参数 high=h = stack[top--]; t = arr[l]; while (l < h) { while (l < h&&arr[h] >= t)--h; arr[l] = arr[h]; while (l < h&&arr[l] <= t)++l; arr[h] = arr[l]; } arr[l] = t; //左边的都更小,右边的都更大,但也别忘记最后中间值的赋值 mid = l; //mid相当于接受partition()的返回值 if (low < mid - 1) { //判断是否还需要继续划分了 stack[++top] = mid - 1; stack[++top] = low; } if (mid + 1 < high) { stack[++top] = high; stack[++top] = mid + 1; } } } int main() { int i, arr[10] = {21,45,76,68,54,89,94,14,37,5}; quickSort(arr, 0, 9); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
选择排序
- 思想:每趟选取最小的元素放在待排序序列的最前方。
- 时间复杂度:
。且不管待排序序列是否有序,时间复杂度都不变。
- 空间复杂度:O(1)。
- 代码实现:
#include<stdio.h> void selectSort(int arr[],int n) { int i, j, min,t; for (i = 0; i < n-1; i++) { min = i; //循环内只记录最小元素的下标,循环结束后再判断是否交换 for (j = i + 1; j < n; j++) { if (arr[j] < arr[min]) { min = j; } } if (min != i) { t = arr[min]; arr[min] = arr[i]; arr[i] = t; } } } int main() { int i, arr[10] = {21,45,76,68,54,89,94,14,37,5}; selectSort(arr, 10); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
堆排序
- 思想:大根堆的每个结点上的值都大于它的孩子节点上的值,这样堆顶就是最大值。每趟将堆顶元素(最大元素)与待排序序列的最后一个位置交换位置。
- 时间复杂度:建堆的过程,关键字的比较次数不超过4n,故建堆的时间复杂度=O(n)。排序的过程中树的深度为:
,每个结点下沉的时间复杂度为:
,一共有n个结点,所以堆排序的时间复杂度为
。
- 空间复杂度:O(1)。
- 代码实现:
#include<stdio.h> void adjust(int arr[], int i, int n) { //i是待调整结点的下标,n是调整范围的最后一个结点下标 int t = arr[i], j = i * 2; //t先保存下待调整元素的值,j是左孩子结点的下标 while (j <= n) {//如果当前节点存在 if (j + 1 <= n && arr[j + 1] > arr[j]) {//如果当前结点存在右兄弟且值更大,则将有兄弟记为当前节点 j++; } if (t >= arr[j]) {//当待调整结点的值比最大孩子还要大时,则不需要调整。 break; } arr[j / 2] = arr[j]; //将最大孩子节点的值放到父亲节点上 j *= 2; //再向下一层探测,直到找到待调整元素应该存在的位置 } arr[j / 2] = t;//将待调整元素存放 } void heapSort(int arr[], int n) { for (int i = n - 1; i >= 0; i--) { //因为堆下标是从0开始的,先把数组全部的元素向后移动一个位置 arr[i + 1] = arr[i]; } int i,t; for (i = n / 2; i >= 1; i--) { //从最后一个分支节点构建堆 adjust(arr, i, n); } for (i = n - 1; i >= 1; i--) { //i记录的是将大顶堆以待排序序列的最后一个元素交换位置后 t = arr[i]; //剩下的待排序序列的最后一个元素的下标 arr[i] = arr[1]; arr[1] = t; adjust(arr, 1, i); //交换后再将大根堆调整合格 } for (i = 1; i <= n; i++) { //之前将数组向后移动了,最后要把数组向前移动回来 arr[i - 1] = arr[i]; } } int main() { int i, arr[10] = {21,45,76,68,54,89,94,14,37,5}; heapSort(arr,10); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
归并排序
- 思想:将n个元素看成n个有序的子表,每个子表长度为1,然后两两合并,得到
个长度为2或1的有序表;再两两归并,直至合成一个长度为n的有序表为止。
- 时间复杂度:
。
- 空间复杂度:
。
- 代码实现:
#include<stdio.h> int temp[100]; void merge(int arr[], int low, int mid, int high) { int left = low, right = mid + 1, index = low; for (int i = low; i <= high; i++) { temp[i] = arr[i]; } while (left <= mid && right <= high) { if (temp[left] <= temp[right]) { arr[index++] = temp[left++]; } else { arr[index++] = temp[right++]; } } while (left <= mid) { arr[index++] = temp[left++]; } while (right <= high) { arr[index++] = temp[right++]; } } void mergeSort(int arr[], int low, int high) { if (low >= high) { return; } int mid = (low + high) / 2; mergeSort(arr, low, mid); mergeSort(arr, mid + 1, high); merge(arr, low, mid, high); } int main() { int i, arr[10] = {21,45,76,68,54,89,94,14,37,5}; mergeSort(arr, 0, 9); for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
总结
算法 | 最好的时间复杂度 | 平均时间复杂度 | 最坏的时间复杂度 | 空间复杂度 | 是否稳定 |
直接插入排序 | 是 | ||||
冒泡排序 | 是 | ||||
简单选择排序 | 否 | ||||
希尔排序 | | 否 | |||
快速排序 | 否 | ||||
堆排序 | 否 | ||||
归并排序 | 是 |