前言
对几种排序方法进行简单描述和放上代码
希尔排序
把一个待排序的数组划分为几个部分,每次拿第i+1部分第j位的数字和第i部分第j位的数字进行比较,如果前者比较小就互换位置,否则不用。
// 希尔排序
void shellSort(int *arr, int len) {
int increment = len;
do {
increment = increment / 3 + 1; // 划分每次比较每部分的长度
for (int i = increment; i < len; i++) {
if (arr[i] < arr[i-increment]) { // 如果后一部分小于前一部分
int t = arr[i], j;
for (j = i - increment; j >= 0 && t < arr[j]; j -= increment) {
arr[j+increment] = arr[j]; // 让大数往后挪
}
arr[j+increment] = t;
}
}
} while (increment > 1);// 如果比较长度不是大于1就没有继续比较下去的必要
}
平均:O(nlogn)~O(n2) 最好:O(n1.3) 最坏:O(n2)
堆排序
堆排序的步骤:
初始化堆:从堆的中间开始整理,左右子树的值不得大于原节点,若大于原节点,则交换,若有交换,还要顺着被交换子节点往下继续找有没有大于原节点的子节点;最后序列中最大的数字会在堆顶;
排序:把堆顶元素和最后一个元素交换位置,再重建最大堆。
void HeapAdjust(int *arr, int first, int end) {
int t = arr[first];
// 从first结点的左子树开始找起,往下层子树向下遍历
for (int i = 2 * first; i <= end; i *= 2) {
// 左右子树中找到最大的那个
if (i < end && arr[i] < arr[i+1]) {
++i;
}
// 判断最大子节点的值是否比原结点的大
if (t >= arr[i]) {
break;
}
// 大的子节点往上移动
arr[first] = arr[i];
first = i; // 往下移动
}
// 遍历到最后的节点赋值为原节点的值
arr[first] = t;
}
void HeapSort(int *arr, int len) {
// 初始化堆,从倒数第二层开始遍历
for (int i = len / 2; i >= 0; i--) {
HeapAdjust(arr, i, len - 1);
}
// 排序
for (int i = len - 1; i > 0; i--) {
// 把堆顶最大值换到数组后面
swap(arr[0], arr[i]);
// 重构最大堆,重新找最大值
HeapAdjust(arr, 0, i - 1);
}
}
O(nlogn)
归并排序
看着书里的思路写了一遍,被bug搞懵逼了半个钟……
思路就是,把一个数组不断对半分,到只有一个的时候不需要对半,只有两个的时候排个序,i个的时候在i-1个的基础上排序。
bug卡在归并的递归写法是把数组1的数字归并到数组2,一开始就是数组1、2分不清……
代码和具体解释如下
// 一开始的时候调用:MergeSort(arr, arr, 0, ARRAYSIZE - 1);
即把arr的数字归并排序到arr
// 把t_arr的内容直接归并到arr
void MergeSort(int *t_arr, int *arr, int first, int end) {
// 只有一个数字时把t_arr的内容直接归并到arr
if (first == end) {
arr[first] = t_arr[first];
return;
}
// 把要归并的内容对半分
int mid = first + (end - first) / 2;
int p_arr[ARRAYSIZE] = {0}; // 建立一个中间数组
// 把 t_arr两边的内容各自归并到p_arr
MergeSort(t_arr, p_arr, first, mid);
MergeSort(t_arr, p_arr, mid + 1, end);
// 把p_arr的内容排好序放进arr
Merge(p_arr, arr, first, mid + 1, end);
}
// 把已经两边各自有序的p_arr综合排序放进arr
void Merge(int *p_arr, int *arr, int first, int next_first, int end) {
int i = first, j = next_first, k = first;
for (; i < next_first && j <= end; k++) {
if (p_arr[i] <= p_arr[j]) {
arr[k] = p_arr[i++];
} else{
arr[k] = p_arr[j++];
}
}
for (; j <= end; j++, k++) {
arr[k] = p_arr[j];
}
for (; i < next_first; i++, k++) {
arr[k] = p_arr[i];
}
}
O(nlogn)
快速排序
之前的博客写过。
以最右边的元素为基准,从左往右找大于基准的数字和从右往左找小于或等于基准的数字并进行交换,直到左右两个的下标相遇(相等),就返回这个下标。需要O(n)的存储空间。
O(nlogn)
int partition1(int *arr, int left, int right) {
int pivot = arr[right];
int low = left, high = right - 1;
while (low < high) {
while (low < high && arr[low] <= pivot)
++low;
while (high > low && arr[high] > pivot)
--high;
if (low < high)
swap(&arr[low], &arr[high]);
}
// 把主元放进一个合适的地方并返回主元下标
if (arr[high] > pivot) {
swap(&arr[high], &arr[right]);
return high;
}
else {
swap(&arr[high + 1], &arr[right]);
return high + 1;
}
}
选择排序
从第一个数字开始遍历,在后面的数字中找到最小的数字与之交换位置。
O(n2)
插入排序
从第2个数字开始遍历,若该数字比前一个数字小,则从前一个数字开始往后移一位直到有一个合适的位置可以把该数字放进。
O(n2)
冒泡排序
这个就是不断地两位两位数字比较并交换位置。
O(n2)
参考
《大话数据结构》~