排序算法
排序算法是能将一串数据依照特定排序方式进行排列的算法。排序算法在工程中有大量的应用,也是很多基于有序性的算法的前提。
稳定排序
对于一个排序算法,如果任意两个元素a[i]
和a[j]
在排序前的线性表中满足i < j
且a[i] = a[j]
,在排序后a[i]
仍在a[j]
之前,则称这个排序算法为 稳定排序(stable sort)。
插入排序
插入排序是一种非常直观的排序算法,它的基本思路是将线性表分为已排序的前半部分和待排序的后半部分,从待排序部分选出第一个元素,插入到已排序部分的对应位置中,直到全部记录都插入到已排序部分中。
插入排序每次插入的时间复杂度为 O(n),一共执行n-1
次,因此总体时间复杂度为O(n2)。在插入时查找插入的过程可以使用折半查找算法将查找位置的复杂度优化到O(log n)
,但因为还需要O(n)
的时间复杂度来在顺序表上执行移动操作,所以总体的时间复杂都依然是O(n2)。
- 将数组分成“已排序区”和“待排序区”
- 将“已排序区”第一个元素,向前插入到“待排序区”中
- 直到“待排序区”没有元素为止
/*************************************************************************
> File Name: 6.insertion_sort.c
> Author: 陈杰
> Mail: 15193162746@163.com
> Created Time: 2021年04月03日 星期六 14时07分42秒
> 插入排序法
************************************************************************/
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
/*交换算法*/
#define swap(a, b) {\
__typeof(a) _temp = a;\
a = b, b = _temp;\
}
/*
* 插入排序法
* @param data: 要排序的数组序列指针
* @param length: 序列长度
* */
void insertion_sort(int *data, int length) {
for(int i = 1; i < length; i++) {
for(int j = i; j > 0; j--) {
if(data[j] < data[j - 1]) {
swap(data[j], data[j-1]);
}else break;
}
}
}
/*
* 打印数组
* @param data: 要排序的数组序列指针
* @param length: 序列长度
* */
void output(int *data, int length) {
for(int i = 0; i < length; i++) {
i == 0 || printf(" ");
printf("%2d", data[i]);
}
printf("\n");
}
int main() {
srand(time(0));
#define MAX_N 20
int num[MAX_N];
for(int i = 0; i < MAX_N; i++) num[i] = rand() % 100;
output(num, MAX_N);
insertion_sort(num, MAX_N);
output(num, MAX_N);
#undef MAX_N
return 0;
}
冒泡排序
和插入排序算法不同,冒泡排序算法是一种基于交换的排序算法。基于交换的排序,是指根据线性表中两个元素关键字的比较结果来对换这两个元素在序列中的位置。
冒泡排序算法的基本思想为:假如待排序线性表的长度为n
,从前往后两两比较相邻元素的关键字,若a[i-1] > a[i]
,则交换他们,直到线性表比较完成。每趟交换以后最后一个元素一定是最大的,不再参与下一趟交换。也就是对于第i
趟交换,只需要比较到a[n-i]
即可。直到一趟比较内没有进行交换,算法结束。时间复杂度和插入排序一样,也为O(n2)。
- 将数组分成“已排序区”和“待排序区”
- 从头到尾扫描“待排序区”,若前面元素比后面元素大,则交换
- 每一轮都会将“待排序区”中最大的放到“已排序区”的开头
- 直到“待排序区”没有元素为止
/*************************************************************************
> File Name: 6.insertion_sort.c
> Author: 陈杰
> Mail: 15193162746@163.com
> Created Time: 2021年04月03日 星期六 14时07分42秒
> 冒泡排序法
************************************************************************/
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
/*交换算法*/
#define swap(a, b) {\
__typeof(a) _temp = a;\
a = b, b = _temp;\
}
/*
* 冒泡排序法
* @param data: 要排序的数组序列指针
* @param length: 序列长度
* */
void bubble_sort(int *data, int length) {
for(int i = 0; i < length - 1; i++) {
int swapped = 0;
for(int j = 0; j < length - i - 1; j++) {
if(data[j] > data[j + 1]) {
swapped = 1;
swap(data[j], data[j + 1]);
}
}
if(!swapped) break; // 未交换,说明待排序区的元素已经有序
}
}
/*
* 打印数组
* @param data: 要排序的数组序列指针
* @param length: 序列长度
* */
void output(int *data, int length) {
for(int i = 0; i < length; i++) {
i == 0 || printf(" ");
printf("%2d", data[i]);
}
printf("\n");
}
int main() {
srand(time(0));
#define MAX_N 20
int num[MAX_N];
for(int i = 0; i < MAX_N; i++) num[i] = rand() % 100;
output(num, MAX_N);
bubble_sort(num, MAX_N);
output(num, MAX_N);
#undef MAX_N
return 0;
}
归并排序
归并的意思是将两个有序的线性表组合成一个新的有序线性表。对于归并排序,若当前要排序的区间为a[0]...a[n-1]
,则首先让a[0]...a[(n-1)/2]
和a[(n-1)/2+1] ... a[n-1]
这两个区间内的元素有序,再将这两个区间合并成一个更大的有序区间,直到整个线性表都被排序完成。
归并排序一共需要进行O(log n)
层归并操作,每层归并操作的总时间复杂度为O(n)
,英雌总体的时间复杂度为O(n log n)
。和其他排序有所不同,为了实现归并操作,每次合并都需要开辟额外的空间来临时保存合并后的排序结果,总共需要开辟n
个元素的空间,所以归并排序的空间复杂度为O(n)
。
/*************************************************************************
> File Name: 6.merge_sort.c
> Author: 陈杰
> Mail: 15193162746@163.com
> Created Time: 2021年04月03日 星期六 14时07分42秒
> 归并排序法
************************************************************************/
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
/*
* 归并排序法
* @param data: 要排序的数组序列指针
* @param left: 排序开始位置
* @param right: 数组结束位置
* */
void merge_sort(int *data, int left, int right) {
if(left == right) return;
int mid = (left + right) / 2;
// 大数组分为两个小数组进行排序
merge_sort(data, left, mid);
merge_sort(data, mid + 1, right);
// 数组合并到新数组中
int temp[right - left + 1];
int x = left, y = mid + 1, loc = 0; // 两个数组及新数组的指针
while(x <= mid || y <= right) {
if(x <= mid && (y > right || data[x] <= data[y])) temp[loc++] = data[x++];
else temp[loc++] = data[y++];
}
// 数组拷贝
for(int i = left; i <= right; ++i) data[i] = temp[i - left];
}
/*
* 打印数组
* @param data: 要排序的数组序列指针
* @param length: 序列长度
* */
void output(int *data, int length) {
for(int i = 0; i < length; i++) {
i == 0 || printf(" ");
printf("%2d", data[i]);
}
printf("\n");
}
int main() {
srand(time(0));
#define MAX_N 20
int num[MAX_N];
for(int i = 0; i < MAX_N; i++) num[i] = rand() % 100;
output(num, MAX_N);
merge_sort(num, 0, MAX_N-1);
output(num, MAX_N);
#undef MAX_N
return 0;
}
不稳定排序
对于一个排序算法,如果任意两个元素a[i]
和a[j]
在排序前的线性表中满足i < j
且a[i] = a[j]
,在排序后a[i]
并不一定a[j]
之前,则称这个排序算法为 不稳定排序(unstable sort)
选择排序
选择排序的思想是,每趟从线性排序区域选取关键字最小的元素,将其放到已排序区域的最后。因为每趟可以让待排序区域的元素数了减少一个,所以总共需要n - 1
趟操作就可以将整个线性表排序完成。很显然,选择排序的时间复杂度也是 O(n2)。
在每次查找关键字最小的元素时,可以使用堆对效率进行优化,使用堆来优化的选择排序就是堆排序。由于一共要查找n
次最小值,每次查找的时间为O(log n)
,所以堆排序的时间复杂度为O(nlogn)
。
- 将数组分成“已排序区”和“待排序区”
- 每一轮从“待排序区”中选择一个最小的元素放到“已排序区”的尾部
- 直到“待排序区”为空
/*************************************************************************
> File Name: 6.selection_sort.c
> Author: 陈杰
> Mail: 15193162746@163.com
> Created Time: 2021年04月03日 星期六 14时07分42秒
> 选择排序法
************************************************************************/
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
/*交换算法*/
#define swap(a, b) {\
__typeof(a) _temp = a;\
a = b, b = _temp;\
}
/*
* 选择排序法
* @param data: 要排序的数组序列指针
* @param length: 序列长度
* */
void selection_sort(int *data, int length) {
for(int i = 0; i < length - 1; i++) {
int ind = i;
for(int j = i + 1; j < length; j++) {
if(data[ind] > data[j]) ind = j;
}
if(ind != i) swap(data[i], data[ind]);
}
}
/*
* 打印数组
* @param data: 要排序的数组序列指针
* @param length: 序列长度
* */
void output(int *data, int length) {
for(int i = 0; i < length; i++) {
i == 0 || printf(" ");
printf("%2d", data[i]);
}
printf("\n");
}
int main() {
srand(time(0));
#define MAX_N 20
int num[MAX_N];
for(int i = 0; i < MAX_N; i++) num[i] = rand() % 100;
output(num, MAX_N);
selection_sort(num, MAX_N);
output(num, MAX_N);
#undef MAX_N
return 0;
}
快速排序
快速排序是目前应用最广泛的排序算法之一。它的基本思想是,每次从排序区域选取一个元素(我们在后面的课程中都是选取第一个)作为基准记录,所有比基准记录小的元素都在基准记录的左边,而所有比基准记录大的元素都在基准记录的右边。之后分别对基准记录的左边和右边两个区间进行快速排序,直至将整个线性表排序完成。
/*************************************************************************
> File Name: 6.quick_sort.c
> Author: 陈杰
> Mail: 15193162746@163.com
> Created Time: 2021年04月03日 星期六 14时07分42秒
> 快速排序法
************************************************************************/
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
/*
* 快速排序法
* @param data: 要排序的数组序列指针
* @param left: 排序开始位置
* @param right: 数组结束位置
* */
void quick_sort(int *data, int left, int right) {
if(left > right) return;
int pivot = data[left]; // 选择一个基准点
int low = left, high = right; // 两个指针指向最开始的最高和最低位
while(low < high) {
while(low < high && data[high] >= pivot) high--;
// 当高位指针的值小于基准值时,高位的值给低位
data[low] = data[high];
while(low < high && data[low] <= pivot) low++;
// 当低位指针的值大于基准值时,低位的值给高位
data[high] = data[low];
}
// 当高位与低位重合时,当前为赋值为基准值,即为中间值
data[low] = pivot;
quick_sort(data, left, low - 1);
quick_sort(data, low + 1, right);
}
/*
* 打印数组
* @param data: 要排序的数组序列指针
* @param length: 序列长度
* */
void output(int *data, int length) {
for(int i = 0; i < length; i++) {
i == 0 || printf(" ");
printf("%2d", data[i]);
}
printf("\n");
}
int main() {
srand(time(0));
#define MAX_N 20
int num[MAX_N];
for(int i = 0; i < MAX_N; i++) num[i] = rand() % 100;
output(num, MAX_N);
quick_sort(num, 0, MAX_N-1);
output(num, MAX_N);
#undef MAX_N
return 0;
}
快速排序的时间复杂度不是稳定的,可以证明快速排序的平均时间复杂度为O(n log n)
,最坏情况为O(n2) (当序列的排序顺序与预期的顺序完全相反时,快速排序退化为选择排序)。可以通过随机选择基准记录来竟可能避免最坏情况的出现。同时将前半部分进行手动交换,来优化快速排序
/*************************************************************************
> File Name: 6.quick_sort.c
> Author: 陈杰
> Mail: 15193162746@163.com
> Created Time: 2021年04月03日 星期六 14时07分42秒
> 快速排序法
************************************************************************/
#include<stdio.h>
#include <stdlib.h>
#include <time.h>
/*交换算法*/
#define swap(a, b) {\
__typeof(a) _temp = a;\
a = b, b = _temp;\
}
/*
* 快速排序法
* @param data: 要排序的数组序列指针
* @param left: 排序开始位置
* @param right: 数组结束位置
* */
void quick_sort(int *data, int left, int right) {
while(left < right) {
int pivot = data[(left + right) >> 1]; // 选择一个基准点
int low = left, high = right; // 两个指针指向最开始的最高和最低位
do {
while(data[low] < pivot) low++;
while(data[high] > pivot) high--;
if(low <= high) {
swap(data[low], data[high]);
low++, high--;
}
}while(low <= high);
quick_sort(data, left, high);
left = low;
}
}
/*
* 打印数组
* @param data: 要排序的数组序列指针
* @param length: 序列长度
* */
void output(int *data, int length) {
for(int i = 0; i < length; i++) {
i == 0 || printf(" ");
printf("%2d", data[i]);
}
printf("\n");
}
int main() {
srand(time(0));
#define MAX_N 20
int num[MAX_N];
for(int i = 0; i < MAX_N; i++) num[i] = rand() % 100;
output(num, MAX_N);
quick_sort(num, 0, MAX_N-1);
output(num, MAX_N);
#undef MAX_N
return 0;
}