1.交换排序
基本思想:所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
首先我们先介绍并创建两个函数,后面要用
第一个定义了一个名为Swap的函数
实现了交换两个整数指针所指向的值
void Swap(int* p1, int* p2) { int tmp = *p1; *p1 = *p2; *p2 = tmp; }
第二个三数取中
定义了一个名为GetMidi的函数用于确定三个位置begin、midi和end在数组a中的中间值索引,确定并返回中间值的索引
int GetMidi(int* a, int begin, int end) { int midi = (begin + end) / 2; // begin midi end 三个数选中位数 if (a[begin] < a[midi]) { if (a[midi] < a[end]) return midi; else if (a[begin] > a[end]) return begin; else return end; } else // a[begin] > a[midi] { if (a[midi] > a[end]) return midi; else if (a[begin] < a[end]) return begin; else return end; } }
第三个 实现快速排序算法
函数QuickSort采用递归的方式对数组a进行排序。它接收三个参数:数组a、开始索引begin和结束索引end。
调用partSort函数,它用来进行划分操作,将数组中的元素分为两部分,以一个关键元素作为参考,小于等于关键元素的元素放在关键元素的左边,大于关键元素的元素放在关键元素的右边。partSort函数返回关键元素的索引位置keyi。
递归调用 QuickSort 函数对基准值左边的子数组进行排序,起始位置为 begin,结束位置为 keyi - 1。这样可以保证基准值左边的子数组是有序的。
最后,递归调用 QuickSort 函数对基准值右边的子数组进行排序,起始位置为 keyi + 1,结束位置为 end。这样可以保证基准值右边的子数组是有序的
void QuickSort(int* a, int begin, int end) { if (begin >= end) return; int keyi = partSort(a, begin, end); QuickSort(a, begin, keyi - 1); QuickSort(a, keyi + 1, end); }
2.冒泡排序
代码解析
void BubbleSort(int* a, int n)//冒泡排序 { for (int j = 0; j < n; j++) { bool exchange = false; for (int i = 1; i < n - j; i++) { if (a[i - 1] > a[i]) { Swap(&a[i - 1], &a[i]); exchange = true; } } if (exchange == false) break; } }
冒泡排序的特性总结:
1. 冒泡排序是一种非常容易理解的排序
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:稳定
3.快速排序
快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,
其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
// 假设按照升序对array数组中[left, right)区间中的元素进行排序 void QuickSort(int array[], int left, int right) { if(right - left <= 1) return; // 按照基准值对array数组的 [left, right)区间中的元素进行划分 int div = partion(array, left, right); // 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right) // 递归排[left, div) QuickSort(array, left, div); // 递归排[div+1, right) QuickSort(array, div+1, right); }
上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,同学们在写递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。
将区间按照基准值划分为左右两半部分的常见方式有:
1. hoare版本
在快速排序算法中,需要在找到左边比关键值大的元素和右边比关键值小的元素之后才进行元素交换,以确保左边的元素都比关键值小,右边的元素都比关键值大。但是在代码中目前的交换步骤存在问题,因为在while循环结束之后直接进行了一次元素交换,而应该是在找到左右指针位置后再进行交换。
int QuickSort1(int* a, int begin, int end) { int midi = GetMidi(a, begin, end); Swap(&a[midi], &a[begin]); int left = begin, right = end; int keyi = begin; while (left < right) { // 右边找小 while (left < right && a[right] >= a[keyi]) { --right; } // 左边找大 while (left < right && a[left] <= a[keyi]) { ++left; } Swap(&a[left], &a[right]); } Swap(&a[left], &a[keyi]); return left; }
2. 挖坑法
代码解析
挖坑法:
使用挖坑法的思想,即每次在处理完左右指针位置的元素后,将最初选择的“坑”填入正确的位置。
函数逻辑:
首先在数组中选择基准值(pivot)并与起始位置的元素交换,这里选择的基准值为a[begin]。
使用两个指针begin和end分别指向数组的起始和末尾,开始移动指针以找到需要交换的元素。
当begin小于end时,进行以下操作:
从右侧开始,找到第一个比基准值小的元素,将其填入“坑”中,同时更新“坑”的位置和end指针的位置。
从左侧开始,找到第一个比基准值大的元素,将其填入之前右侧留下的“坑”中,同时更新“坑”的位置和begin指针的位置。
最后,将基准值填入最后的“坑”中,返回该“坑”的位置,用于分割左右子数组。
返回值:
函数返回的是基准值在排序后的位置,用于在递归调用中分割左右子数组。
//2.挖坑法 int QuickSort2(int* a, int begin, int end) { int midi = GetMidi(a, begin, end); Swap(&a[midi], &a[begin]); int key = a[begin]; int hole = begin; while (begin < end) { // 右边找小,填到左边的坑 while (begin < end && a[end] >= key) { --end; } a[hole] = a[end]; hole = end; // 左边找大,填到右边的坑 while (begin < end && a[begin] <= key) { ++begin; } a[hole] = a[begin]; hole = begin; } a[hole] = key; return hole; }
3. 前后指针版本
代码解析
将数组分成两个子数组,左边的子数组中的元素都小于等于中间元素,右边的子数组中的元素都大于等于中间元素,并返回中间元素的索引
int QuickSort3(int* a, int begin, int end) { int midi = GetMidi(a, begin, end); Swap(&a[midi], &a[begin]); int keyi = begin; int prev = begin; int cur = prev + 1; while (cur <= end) { if (a[cur] < a[keyi] && ++prev != cur) Swap(&a[prev], &a[cur]); ++cur; } Swap(&a[prev], &a[keyi]); keyi = prev; return keyi; }