for (i = 0 ; i < len - 1 ; i++) { int min = i; for (j = i + 1; j < len; j++){ if (arr[j] < arr[min]) //找到目前最小值 min = j; //记录最小值 swap(&arr[min], &arr[i]);} //做交换 } }
算法分析
选择排序是一种简单直观的排序算法;
任何数据的时间复杂度都是O(n²) ;
空间复杂度为O(1)。
插入排序
原理
插入排序类似于给扑克牌排序。通过构建有序数列,将未排序的元素从后往前找位置并插入。
将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
动图演示
代码
1 2 3 4 5 6 7 8 9 10 11 12
voidinsertion_sort(int arr[], int len){ int i,j,key; for (i=1;i<len;i++){ key = arr[i]; j=i-1; while((j>=0) && (arr[j]>key)) { arr[j+1] = arr[j]; j--; } arr[j+1] = key; } }
voidmerge(int *arr, int L, int M, int R){ int LEFT_SIZE = M - L + 1; int RIGHT_SIZE = R - M; int *left = (int *)malloc(LEFT_SIZE * sizeof(int));//开辟左子数组 memset(left, 0, LEFT_SIZE * sizeof(int)); int *right = (int *)malloc(RIGHT_SIZE * sizeof(int));//开辟右子数组 memset(right, 0, RIGHT_SIZE * sizeof(int));
int i, j, k; // 以 M 为分割线,把原数组分成左右子数组 for (i = L; i <= M; i++) {left[i - L] = arr[i];} for (j = M + 1; j <= R; j++) {right[j - M - 1] = arr[j];} // 再合并成一个有序数组(从两个序列中选出最小值依次插入) i = 0; j = 0; k = L; while (i < LEFT_SIZE && j < RIGHT_SIZE){arr[k++] = left[i] < right[j] ? left[i++] : right[j++];} while (i < LEFT_SIZE) {arr[k++] = left[i++];} while (j < RIGHT_SIZE) {arr[k++] = right[j++];}
//释放内存 free(left); left = NULL; free(right); right = NULL; }
voidmerge_sort(int *arr, int L, int R){ if (L == R) return; //递归终止条件,分无可分 // 将 arr[L..R] 平分为 arr[L..M] 和 arr[M+1..R]. int M = (L + R) >> 1; //(分别递归地将子序列排序为有序数列)先分割到最小子区间,再反过来一步步合并成一个完整有序数组 merge_sort(arr, L, M);//左边 merge_sort(arr, M + 1, R);//右边 // 将两个排序后的子序列再归并到 arr merge(arr, L, M, R); }
//调整剩余元素,构成大顶堆(全是根据遍历父节点来调整每一个二叉部分) voidheapify(int tree[], int n, int parent){ //n 表示序列长度,i 表示父节点下标 if (parent >= n) return; //左侧子节点下标 int left = 2 * parent + 1; //右侧子节点下标 int right = 2 * parent + 2;
int max = parent; if (left < n && tree[left] > tree[max]) max = left; if (right < n && tree[right] > tree[max]) max = right; if (max != parent){//如果最大值不是当前父节点,则交换最大值到上面,并继续递归调用调整下面相连的子二叉树 swap(tree, max, parent); heapify(tree, n, max); //递归调整被交换的下一个父亲节点 } }
//将数组下标当作完全二叉树每个节点下标来处理(从左至右,从上至下排序号) voidbuild_heap(int tree[], int n){ //树最后一个节点下标 int last_node = n - 1; //确定最后一个节点 的 父节点下标 int parent = (last_node - 1) / 2; //开始从最后一个父节点开始,往前遍历每个父节点,并调整值的顺序,最终当前最大值会到堆顶 int i; //第一次构建大顶堆需要遍历所有父节点,后面每次调整只要传入根节点即可(里面会递归调整发生值改变的所在子二叉树) for (i = parent; i >= 0; i--){ heapify(tree, n, i); } }
voidheap_sort(int tree[], int n){ //构建堆 build_heap(tree, n); int i; //i代表最后一个元素下标,也是当前未调整的数组长度 //每次将堆化好的堆顶元素放到最后,并调整剩下的元素 for (i = n - 1; i >= 0; i--){ // 将堆顶元素与最后一个元素交换 swap(tree, i, 0); //调整大顶堆(剩下的元素),永远保证大顶堆有序 heapify(tree, i, 0); //构建好堆以后 每次调整,只要传入根节点0即可 } }
voidbucket_sort(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; counting_sort(buckets[i], counts[i]); // 拼接非空的桶内数据,得到最终的结果 for (j = 0; j < counts[i]; j++) { arr[index++] = buckets[i][j]; } } }
voidradix_sort(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 ++; } intqueue[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; } }
算法分析
基数排序是稳定排序,适用于关键字取值范围固定的排序;
假设给定 n 个数,它的最高位数是 d,基数(也就是桶的个数)为 r,那么可以这样理解:共进行 d 趟排序,每趟排序都要对 n 个数据进行分配,再从 r 个桶中收集回来。所以算法的时间复杂度为 O(d(n+r)),在整数的排序中,r = 10,因此可以简化成 O(dn),是线性阶的排序。
需要创建 r 个桶的额外空间,以及 n 个元素的额外空间,所以基数排序的空间复杂度为 O(n+r)。