总结记录一下,C++实现各种排序算法,加深理解。参考《大话数据结构》
冒泡排序(Bubble Sort)是一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直达没有反序的记录位置。
void BubbleSort(vector &nums) {
for (int i = 0; ii; j--) {
if (nums[j]
冒泡排序优化
当一轮比较中,没有任何数据交换,这就说明此序列已经有序,不需要再继续后面的循环判断工作了。为了实现这个想法,改进一下代码,增加标记变量flag.
void BubbleSort1(vector &nums) {
for (int i = 0, flag = 1; i < nums.size() - 1 && flag == 1; i++) {
flag = 0;
for (int j = nums.size() - 1; j>i; j--) {
if (nums[j]
优化以后的冒泡排序算法当数组已经排好序时,仅需要n-1此比较。
该排序算法的时间复杂度为O(n^2)。
简单选择排序(Simple Selection Sort)通过n-i-1次关键字的比较,从n-i个记录中选出关键字最小的记录,并和第i(0<=i<=n)个记录交换之。
void SelectSort(vector &nums) {
int i, j, index, t;
for (i = 0; i < nums.size(); i++) {
index = i;
for (j = i + 1; j < nums.size(); j++) {
if (nums[j] < nums[index]) {
index = j;
}
}
t = nums[i];
nums[i] = nums[index];
nums[index] = t;
}
}
选择排序的优点是交换移动数据的次数相当少。
该排序算法的时间复杂度为O(n^2)。
直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
void InsertSort(vector &nums) {
int i, j, flag;
for (i = 0; i < nums.size()-1; i++) {
flag = nums[i + 1];
for (j = i;j>=0&& flag < nums[j];j--)
nums[j + 1] = nums[j]; //记录后移
nums[j+1] = flag; //插入到正确的位置
}
}
最好的情况下,只需要比较n-1次,不需要移动元素。
该排序算法的时间复杂度为O(n^2)。
基本有序:小的关键字在前面,大的关键字在后面,不大不小的关键字在中间。
将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序的。
因此,希尔排序就是按照一个增量,从原序列中取出子序列,对子序列使用插入排序。再缩小增量,重复操作,直到增量变为1,此时的子数组就是原数组,再使用一次插入排序即可。
void ShellSort(vector &nums) {
int i, j, k, t;
int inc = nums.size();
while (1) {
inc = inc / 3 + 1;
for (int i = inc; i < nums.size(); i++) {
t = nums[i]; //存需要插入的元素
for (j = i-inc; j >= 0&&t
希尔排序的复杂度是O(n^(3/2))
堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
堆排序算法就是利用堆进行排序的方法。它的基本思想是将待排序的序列构成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构成一个堆,这样就会得到n个元素中的次大值。如此反复执行,便能得到一个有序序列了。
void HeapAdjust(vector &nums, int s, int m) {//调整s结点,使得该位置的结点满足大顶堆的条件
int i, temp;
temp = nums[s];
for (int i = 2 * s; i <= m; i = i * 2) {
if (i < m&&nums[i] < nums[i + 1])
i++;
if (temp >= nums[i])
break;
nums[s] = nums[i];
s = i;
}
nums[s] = temp;
}
void HeapSort(vector &nums) {
int i;
for (i = nums.size() / 2; i >= 0; i--) { //从最后一个有叶子结点的结点开始,一直到根。
HeapAdjust(nums, i, nums.size() - 1);
}
int t;
for (i = nums.size() - 1; i > 0; i--) {
t = nums[i];
nums[i] = nums[0];
nums[0] = t;
HeapAdjust(nums, 0, i-1);
}
}
该算法每一轮包括构建堆(logn),取堆顶记录(n)操作,时间复杂度为O(nlogn)。
归并排序(Merging Sort)就是利用归并的思想实现的排序方法。原理是:假定初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2](上取整)个长度为2或1的有序子序列;再两两归并,……,如此重复,直到得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
递归实现归并排序
void Merge(vector &nums, int s, int m, int t) {
vector copy_nums=nums; //把数组的[s,m]和[m+1,t]段进行合并
int i = s, j = m + 1, now = s;
while (i <= m && j <= t) {
if (copy_nums[i] <= copy_nums[j]) {
nums[now] = copy_nums[i];
i++;
}
else {
nums[now] = copy_nums[j];
j++;
}
now++;
}
if (i <= m) {
for (; i <= m; i++) {
nums[now] = copy_nums[i];
now++;
}
}
else if (j <= t) {
for (; j <= t; j++) {
nums[now] = copy_nums[j];
now++;
}
}
}
void MSort(vector &nums, int s, int t) { //对数组的[s,t]段进行二路归并
if (s == t)
return;
else {
int m = (s + t) / 2;
MSort(nums, s, m); //分
MSort(nums, m + 1, t); //分
Merge(nums,s,m,t); //并
}
}
void MergeSort(vector &nums) { //调用该函数进行排序
MSort(nums, 0, nums.size() - 1);
}
算法的复杂度为O(nlogn)
应用例题:数组中的逆序对
快速排序(Quick Sort)的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录进行排序,以达到整个序列有序的目的。
void QSort(vector &nums,int low,int high) {
if (low >= high)
return;
int pivot = nums[low];
int left = low, right = high;
while (left < right) {//将数组重排,使得pivot的左边的数都比pivot小,右边的数都比pivot大
while (left < right&&nums[high] >= pivot)
right--;
int t = nums[right];
nums[right] = nums[left];
nums[left] = t;
while (left < right&&nums[left] <= pivot)
left++;
t = nums[left];
nums[left] = nums[right];
nums[right] = t;
}
QSort(nums, low, left-1); //此时low和high都为pivot的索引值(下标)
QSort(nums, left + 1, high);
}
void QuickSort(vector &nums) {
QSort(nums, 0, nums.size()-1); //返回排序好以后的结果
}
在最好的情况下,(借助于递归树来理解)算法的时间复杂度是O(nlogn),最坏的情况下,时间复杂度为O(n^2)。同时,快速排序也是一种不稳定的算法。