1. 算法相关名词
稳定:如果a原本在b前面,而a = b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a = b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:排序时数据总的操作次数所用的时间规模。
空间复杂度:排序时在计算机内执行所需的临时存储空间。
2. 排序算法分类
比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
3. 排序算法复杂度
1. 算法描述
(1). 从第2个元素开始,依次取出下一元素Key;
(2). 从已排序的元素中从后往前扫描,如果扫描到的元素大于取出的元素Key,将该元素移动下一位置;
(3). 如果扫描已排序的元素中,某个元素小于或等于元素Key,则将Key插入该元素之后;
2. 动画演示
黄色表示已排序部分,蓝色表示未排序部分,红色表示当前正在处理的key
3. 算法实现
void InsertSort(int arr[],int len){
int i,j;
// 从第3个元素开始
for(i=2;i<len;i++){
// 取出要参与比较的元素
arr[0] = arr[i];
// 从取出元素的前一个元素开始扫描
j = i--;
while(arr[0]<arr[j]){
// 元素后移
arr[j+1] = arr[j];
// 继续往前扫描
j--;
};
// 插入取出比较的元素
arr[j+1] = arr[0];
}
}
注意上面的arr[0],算是一个小技巧,可以使循环的时间减少一半。
1. 算法描述
(1). 比较相邻的元素,如果第一个比第二个大,就交换它们两个;
(2). 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对;
(3). 每一趟下来,都会将一个当前比较大数按顺序排到后面应有的位置,排完所有的趟数后,排序完成。
2. 动画演示
黄色表示已排序部分,蓝色表示未排序部分。
3. 算法实现
void BubbleSort(int arr[], int n){
int i, j,temp;
for(i=1;i<n-1;i++){
// 设立岗哨,判断是否还需要排序
int exchange = 0;
for(j=1;j<n-i-1;j++){
if(arr[j]>arr[j+1]){
// 交换位置
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
// 交换过了
exchange = 1;
}
};
// 结束排序
if(exchange == 0){
break;
}
}
}
1. 算法描述
(1). 从数列中挑出一个元素,称为"基准"(pivot);
(2). 重新排序数列,把所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在操作退出之后,该基准就处于数列的中间位置,这个操作称为分区(partition);
(3). 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列按前两步进行排序;
2. 动画演示
3. 算法实现
int QucikSort(int arr[], int low, int high){
if(low ==high){
return;
};
// 左指针
int i = low;
// 右指针
int j = high;
// 选择第一个元素作为基准
int privot = arr[low];
// 以privot为基准,按左小右大依次进行交换
while(i<j){
// 如果右边的值大于或等于基准,且左、右指针未相遇,则右指针左移
while(arr[j] >=privot && i<j){
--j;
};
// 右边找到比基准小的元素,则将其送到左边
if(i<j){
arr[i] = arr[j];
++i;
};
// 如果左边的值小于或等于基准,且左、右指针未相遇,则左指针右移
while((arr[i]<=privot) && i<j){
++i;
};
// 左边找到比基准大的元素,则将其送到右边
if(i<j){
arr[j] = arr[i];
--j;
};
};
// 将基准放到中间
arr[i] = privot;
if(low<i-1){
QucikSort(arr,low,i-1);
};
if(j+1<high){
QucikSort(arr, j+1, high);
}
}
1. 算法描述
(1). 在未排序序列中找到最小元素,存放到排序序列的起始位置;
(2). 在剩余未排序元素中继续寻找最小元素,放到已排序序列的末尾;
(3). 重复步骤2,直到所有元素排序完毕;
2. 动画演示
黄色表示已排序部分,蓝色表示未排序部分,红色表示从未排序中选择的最小值。
3. 算法实现
void SelectSort(int arr[], int len){
// 外层循环按制轮数
for (int i = 0; i < len; i++){
// 内层循环找出最小值进行交换
int tmp;
for (int j = i; j < len; j++){
if (arr[j] < arr[i]){
tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
}
}
}
1. 算法描述
(1). 将一组数据构建成一个堆;
(2). 调整这个堆,使之成为最大堆,将根结点上最大的数与倒数第一个数进行交换;
(3). 重新调整交换过的堆,将根结点上最大的数与倒数第二个数进行交换;
(4). 重复执行建堆操作,依次与倒数没有交换过的数进行交换。
2. 动画演示
红色代表交换过的,绿色代表调整好了的,蓝色代表正在调整的。
3. 算法实现
// 交换
void swap(int arr[], int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 建堆
void buildHeap(int tree[], int n){
int lastNode = n - 1;
// 计算父节点的位置
int parent = (lastNode-1) / 2;
for (int i = parent; i >= 0; i--){
heapify(tree, n, i);
};
}
// 堆化(数组、节点个数、当前节点下标)
void heapify(int tree[], int n, int p){
// 到达最后一个叶子节点
if (p >= n){
return;
};
// 左孩子索引
int c1 = 2 * p + 1;
// 右孩子索引
int c2 = 2 * p + 2;
// 最大数的下标
int max =-1;
// 如果左子节点的下标未越界,并且左子节点的值大于父节点的值
if (c1 < n && tree[c1] > tree[p]){
max = c1;
};
// 如果右子节点的下标未越界,并且右子节点的值大于父节点的值
if (c2<n && tree[c2] > tree[p]) {}
// 如果左子节点大于父节点,并且右子节点又大于左子节点
if(max !=-1 && tree[c2]>tree[c1]){
// 将子结点中最大的那个索引给max
max = c2;
}
};
// 如果存在子大于父
if (max != -1) {
// 交换
swap(tree, max, p);
// 重建堆
heapify(tree, n, max);
}
}
// 堆排序
void heapSort(int tree[], int n){
// 建立堆
buildHeap(tree, n);
// 从最后一个元素开始交换
for (int i = n - 1; i >= 0; i--){
// 将堆顶存放最大值与最后一个元素做交换
swap(tree, i, 0);
// 做交换以后,前面的n-1个数的堆的性质被破坏,重建推
heapify(tree, i, 0);
}
}
1. 算法描述
(1). 把长度为n的序列看成n个子元素;
(2). 从头到尾依次对两个元素进行归并排序;
(3). 将归并排序后的看成一个整体元素,从头到尾再进行归并排序,直到所有的元素都成为一个归并排序整体。
2. 动画演示
3. 算法实现
// 合并(排序的数组、低位下标、中位下标、高位下标、临时存储数组)
void merge(int arr[], int low, int mid, int high, int temp[]){
// 左子数组开始位置
int i = low;
// 右子数组开始位置
int j = mid + 1;
// 临时下标
int t = 0;
// 将数组分为左、右子数组进行循环
while (i <= mid && j <= high){
// 如果左子数组里的某一个值小于右子数组里的某一个值
if (arr[i] < arr[j]){
// 将下标为i的数组存到临时数组里
temp[t++] = arr[i++];
// 否则左子数组里的某一个值大于或等于右子数组里的某一个值
}else{
// 将下标为j的数组存到临时数组里
temp[t++] = arr[j++];
}
};
// 将左边剩余元素填充进存到临时数组
while (i <= mid){
temp[t++] = arr[i++];
};
// 将右边剩余元素填充进存到临时数组
while (j <= high){
temp[t++] = arr[j++];
};
// 临时下标归零
t = 0;
// 将处理后的数据赋值到原数组中
while (low <= high){
arr[low++] = temp[t++];
}
}
// 合并排序(排序的数组、低位下标、高位下标、临时存储数组)
void mergeSort(int arr[], int low, int high, int temp[]){
// 当动态的低位下标小于动态的高位下标时
if (low < high){
int mid = (low + high) / 2;
// 左子数组融合排序
mergeSort(arr, low, mid, temp);
// 右子数组融合排序
mergeSort(arr, mid + 1, high, temp);
// 已经排序好的子数组有序融合
merge(arr, low, mid, high, temp);
}
}
1. 算法描述
(1). 将待排记录序列以变量X为间隔划分为若干子序列,对子序列分别进行插入排序;
(2). 将变量X按一定的规则减少,再将待排记录序列以变量X为间隔划分成为若干子序列,对子序列分别进行插入排序;
(3). 直到变量X减少为1时,对待排记录序列整体进行一次插入排序。
2. 动画演示
3. 算法实现
// 希尔排序(排序数组、数组长度)
void ShellSort(int arr[], int len){
int i, j, k, tmp;
int gap = len;
do{
// 间隔的选择可以有多种方案,如二分之一
// 这里使用的是业界统一实验平均情况最好的,收敛为1
gap = gap / 3 + 1;
// 按间隔划分为多个组
for (i = gap; i < len; i += gap){
// 每组使用插入排序
k = i;
tmp = arr[k];
for (j = i - gap; (j >= 0) && (arr[j] > tmp); j -= gap){
arr[j + gap] = arr[j];
k = j;
};
arr[k] = tmp;
}
} while (gap > 1);
}
1. 算法描述
(1). 找出待排序列中最大值 max 和最小值 min,算出序列的数据范围 r = max - min + 1,申请辅助空间 C[r];
(2). 遍历待排序列,统计序列中当前值 x 出现的次数,记录于辅助空间C[x - min] ;
(3). 对辅助空间 C[r] 内的统计数字进行计算,每一个统计数字等于与前一个统计数字的和,以确定值为 x 在数组中的位置;
(4). 反向遍历原始数组序列每一个数,设当前数减最小数的值为y,C[y]的值减1为这个数在有序序列中的位置,同一个数每重复出现一次,将对应的C[y]位置减1,遍历完成后所有数即为有序序列。
2. 动画演示
3. 算法实现
void countingSort(int arr[], int n) {
if (arr == NULL){
return;
};
// 定义辅助空间并初始化
int max = arr[0], min = arr[0];
int i;
for (i = 1; i < n; i++) {
if (max < arr[i]) {
max = arr[i];
};
if (min > arr[i]){
min = arr[i];
};
};
int r = max - min + 1;
int C[r];
memset(C, 0, sizeof(C));
// 定义目标数组
int R[n];
// 统计每个元素出现的次数
for (i = 0; i < n; i++){
C[arr[i] - min]++;
};
// 对辅助空间内数据进行计算
for (i = 1; i < r; i++) {
C[i] += C[i - 1];
};
// 反向填充目标数组
for (i = n - 1; i >= 0; i--) {
// 设当前数减最小数的值为y,C[y]的值减1为这个数在有序序列中的位置
// 当前数每重复出现一次,将对应的C[y]位置减1向前推一次
int y = arr[i] - min;
R[--C[y]] = arr[i];
};
// 目标数组里的结果重新赋值
for (i = 0; i < n; i++){
arr[i] = R[i];
}
}
1. 算法描述
(1). 设置固定数量的空桶;
(2). 把数据放在对应的桶内,分别对每个非空桶内数据进行排序;
(3). 拼接非空的桶内数据,得到最终的结果。
2. 动画演示
3. 算法实现
void bucketSort(int arr[], int n, int r) {
if (arr == NULL || r < 1) {
return;
};
// 根据最大/最小元素和桶数量,计算出每个桶对应的元素范围
int max = arr[0], min = arr[0];
int i, j;
for (i = 1; i < n; i++) {
if (max < arr[i]) max = arr[i];
if (min > arr[i]) min = arr[i];
}
int range = (max - min + 1) / r + 1;
// 建立桶对应的二维数组,一个桶里最多可能出现 n 个元素
int buckets[r][n];
memset(buckets, 0, sizeof(buckets));
int counts[r];
memset(counts, 0, sizeof(counts));
for (i = 0; i < n; i++) {
int k = (arr[i] - min) / range;
buckets[k][counts[k]++] = arr[i];
};
int index = 0;
// 分别对每个非空桶内数据进行排序,比如计数排序
for (i = 0; i < r; i++) {
if (counts[i] == 0) {
continue}
};
countingSort(buckets[i], counts[i]);
// 拼接非空的桶内数据,得到最终的结果
for (j = 0; j < counts[i]; j++) {
arr[index++] = buckets[i][j];
}
}
}
1. 算法描述
(1). 将所有待比较数值(非负整数)统一为同样的数位长度,数位不足的数值前面补零;
(2). 比较时分最高位优先法(MSD法)和最低位优先法(LSD法),此处以LSD为例,从最低位(个位)开始,依次进行一次排序;
(3). 从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
2. 动画演示
3. 算法实现
// 基数,范围0~9
const RADIX 10
void radixSort(int arr[], int n) {
// 获取最大值和最小值
int max = arr[0], min = arr[0];
int i, j, l;
for (i = 1; i < n; i++) {
if (max < arr[i]) max = arr[i];
if (min > arr[i]) min = arr[i];
};
// 假如序列中有负数,所有数加上一个常数,使序列中所有值变成正数
if (min < 0) {
for (i = 0; i < n; i++){
arr[i] -= min;
};
max -= min;
};
// 获取最大值位数
int d = 0;
while (max > 0) {
max /= RADIX;
d ++;
};
int queue[RADIX][n];
memset(queue, 0, sizeof(queue));
int count[RADIX] = {0};
for (i = 0; i < d; i++) {
// 分配数据
for (j = 0; j < n; j++) {
int key = arr[j] % (int)pow(RADIX, i + 1) / (int)pow(RADIX, i);
queue[key][count[key]++] = arr[j];
};
// 收集数据
int c = 0;
for (j = 0; j < RADIX; j++) {
for (l = 0; l < count[j]; l++) {
arr[c++] = queue[j][l];
queue[j][l] = 0;
}
count[j] = 0;
};
};
// 假如序列中有负数,收集排序结果时再减去前面加上的常数
if (min < 0) {
for (i = 0; i < n; i++) {
arr[i] += min;
}
}
}