比较排序是比较常见的排序算法,它分为以下几个类:
交换排序:冒泡排序(BubbleSort)和快速排序(QuickSort)。
插入排序:直接插入排序和希尔排序(ShellSort)。
选择排序:选择排序(SelectSort)和堆排序(HeapSort)。
(一)交换排序:
void BubbleSort(int* arry, int size) { for (int i = 0; i < size - 1; i++) { for (int j = 0; j < size - i - 1; j++) { if (arry[j] > arry[j + 1]) { swap(arry[j], arry[j + 1]); } } } } //冒泡排序第一次优化(设置一个标志) void BubbleSort(int* arry, int size) { bool swaped = false; for (int i = 0; i < size; i++) { swaped = false; for (int j = 0; j < size - i - 1; j++) { if (arry[j] > arry[j + 1]) { swap(arry[j], arry[j + 1]); swaped = true; } } if (!swaped) { break; } } } //冒泡排序第二次优化(去除已经有序的区间) void BubbleSort(int* arry, int size) { int lastswappos = size-1; int lastswappos_temp = size-1; for (int i = 0; i < size; i++) { lastswappos = lastswappos_temp; for (int j = 0; j < lastswappos; j++)//每次冒泡到最后一次交换的位置 { if (arry[j] > arry[j + 1]) { swap(arry[j], arry[j + 1]); lastswappos_temp = j; } } if (lastswappos==lastswappos_temp) { break; } } }
冒泡排序是最简单的交换排序,在这里我们给出了2种优化方式。第一种是设置一个标志位标志在一趟排序中是否有过排序,如果没有则就排序完成,这种优化方式对于在一个数组的后半部分是有序的情况下提高了效率。第二种方式也相当于设置一个标志位,只是这个标志位是标志最后一次交换的数的下标。这种方法是有效的去除了已经有序的区间。
不管怎么优化,对于一般的数组冒泡排序的时间复杂度为O(N^2)。
快速排序:
//快速排序 void QuickSort(vector<int> &arry, int left, int right) { if (left >= right) return; int lflag = left; int rflag = right - 1; int tmp = arry[left]; while (lflag < rflag) { while ((lflag < rflag) && arry[rflag] >= tmp) { rflag--; } arry[lflag] = arry[rflag]; while ((lflag < rflag) && arry[lflag] <= tmp) { lflag++; } arry[rflag] = arry[lflag]; } swap(arry[lflag], tmp); QuickSort(arry, left, rflag - 1);//左子区间 QuickSort(arry, rflag + 1, right);//右子区间 }
快速排序是利用了划分子问题的思想。每次选取区间的第一个数据作为中间值,将大于中间值的数据放在右边,否则放在左边。再将中间值的左右边看成一个子区间,递归的划分子区间,直到区间的左边下标大于或者等于右边下标结束。
快速排序是效率比大部分排序算法的速度要快。但是快速排序是一个递归思想,显然,对于内存有限的机器来说它不是一个好的选择。对于很长很长的数组来说也不是一个好的选择,递归的额层数越多,效率越低。
(二)选择排序:
一般的选择排序
void SelectSort(int* arry, int len) { assert(arry); int flag = 0; while (flag != len - 1) { int tmp = arry[flag]; for (int i = flag + 1; i < len - 1; i++) { if (arry[i] < tmp) { swap(arry[i], tmp); } } arry[flag] = tmp; flag++; } }
一般的选择排序算法的时间复杂度为O(N^2)。
堆排序
void AdjustDown(int* a, size_t size, size_t parent)//下调函数 { size_t child = parent * 2 + 1; while (child < size) { if (child + 1 < size&&a[child] < a[child + 1]) ++child; if (a[parent] < a[child]) { swap(a[parent], a[child]); parent = child; child = parent * 2 + 1; } else break; } } void HeapSort(int* a, size_t size) { //建堆 for (int i = (size - 2) / 2; i >= 0; --i) //i不能定义为size_t除法会溢出,翻转成一个很大的数 { AdjustDown(a, size, i); } for (size_t i = 0; i < size; ++i) { swap(a[0], a[size - 1 - i]); AdjustDown(a, size - i - 1, 0); } }
堆排序是利用大顶堆的性质,可以快速的找出最大值。依次找出最大值,排序完成。时间复杂度为
O(N*lgN)。
(三)插入排序
直接插入排序:
void InsertSort(int *a, size_t size) { assert(a); for (size_t i = 1; i < size; ++i) { int tmp = a[i]; size_t j = i; while (j > 0 && tmp < a[j - 1]) { a[j] = a[j - 1]; j--; } a[j] = tmp; } }
在数组前面的有序序列里找到合适的位置将待插入数据插入,时间复杂度为O(N^2)。
希尔排序:
void ShellSort(int *a,int size) { int gap = size / 2; while (1 <= gap) { // 把距离为 gap 的元素编为一个组,扫描所有组 for (int i = gap; i < size; i++) { int j = 0; int temp = a[i]; // 对距离为 gap 的元素组进行排序 for (j = i - gap; j >= 0 && temp < a[j]; j = j - gap) { a[j + gap] = a[j]; } a[j + gap] = temp; } gap = gap / 2; // 减小增量 } }
gap是间隔增量。它控制序列向有序发展。
我们分割待排序记录的目的是减少待排序记录的个数,并使整个序列向基本有序发展。因此,我们需要采取跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
希尔排序的时间复杂度是在O(N^1.25)~O(N^1.66)之间。希尔排序在逆序时效果最好,有序时效果最差,还不如直接排序。
本文出自 “稻草阳光” 博客,转载请与作者联系!
原文地址:http://helloleex.blog.51cto.com/10728491/1773529