【排序算法】交换排序(C++实现)
感谢原博主的总结,转载于:http://blog.****.net/left_la/article/details/8648133
所谓交换,就是根据序列中两个记录值的比较结果来对换这两个记录在序列中的位置。交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。常见的交换排序有冒泡排序(Bubble Sort),鸡尾酒排序(Cocktail Sort),奇偶排序(OddEven Sort),地精排序(Gnome Sort),快速排序(Quick Sort),臭皮匠排序(Stooge Sort),梳排序(Comb Sort),Bogo排序(Bogo sort)。下面介绍前六种:
(一)冒泡排序
最差时间复杂度:O(n^2)
最优时间复杂度:O(n)
平均时间复杂度:O(n^2)
最差空间复杂度:总共O(n),需要辅助空间O(1)
稳定性:稳定
冒泡排序(Bubble Sort),它重复地走访过要排序的数列,一次比较两个元素如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
冒泡排序算法的运作如下:
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
算法示意图:
实现代码:
- void BubbleSort(int *a, int len)
- {
- for (int i=0; i<len; i++)
- {
- for (int j=len-1; j>i; j--)
- {
- if (a[j]<a[j-1])
- swap(a[j], a[j-1]);
- }
- }
- }
(二)鸡尾酒排序
最差时间复杂度:O(n^2)
最优时间复杂度:O(n)
平均时间复杂度:O(n^2)
稳定性:稳定
鸡尾酒排序(Cocktail sort),是冒泡排序的一种变形。它与冒泡排序的不同之处在于排序时是以双向在序列中进行排序。数组中的数字本是无规律的排放,先对数组从左到右进行冒泡排序(升序),把最大值放到最右端,然后对数组从右到左进行冒泡排序(降序),把最小的数字放到最左端。然后再以此类推,以此改变冒泡的方向,并不断缩小未排序元素的范围。直到在一趟双向冒泡后没有发生交换,排序结束。
算法示意图:
实现代码:
- void CocktailSort(int* a, int len)
- {
- int bottom = 0;
- int top = len-1;
- bool swapped = true;
- while (swapped)
- {
- swapped = false;
- for (int i=bottom; i<top; i++)
- {
- if (a[i]>a[i+1])
- {
- swap(a[i], a[i+1]);
- swapped = true;
- }
- }
- top = top-1;
- for (int i=top; i>bottom; i--)
- {
- if (a[i]<a[i-1])
- {
- swap(a[i], a[i-1]);
- swapped = true;
- }
- }
- bottom = bottom+1;
- }
- }
(三)奇偶排序
最差时间复杂度:O(n^2)
稳定性:稳定
奇偶排序(OddEven Sort),是一种相对简单的排序算法,最初发明用于有本地互联的并行计算。此算法通过比较数组中相邻的(奇-偶)位置数字对,如果该奇偶对是错误的顺序(第一个大于第二个),则交换。下一步重复该操作,但针对所有的(偶-奇)位置数字对。如此交替下去,直到不发生交换,则排序结束。
在并行计算排序中,使用该算法,每个处理器对应处理一个值,并仅有与左右邻居的本地互连。所有处理器可同时与邻居进行比较、交换操作,交替以奇-偶、偶-奇的顺序。该算法由Habermann在1972年最初发表并展现了在并行处理上的效率。但在单处理器串行运行此算法,类似冒泡排序,较为简单但效率并不特别高。
算法示意图:
实现代码:
- void OddEvenSort(int *a, int len)
- {
- bool swapped = true;
- while (swapped)
- {
- swapped = false;
- for (int i=0; i<len-1; i=i+2)
- {
- if (a[i]>a[i+1])
- {
- swap(a[i], a[i+1]);
- swapped = true;
- }
- }
- for (int i=1; i<len-1; i=i+2)
- {
- if (a[i]>a[i+1])
- {
- swap(a[i], a[i+1]);
- swapped = true;
- }
- }
- }
- }
(四)地精排序
最差时间复杂度:O(n^2)
最优时间复杂度:O(n)
平均时间复杂度:O(n^2)
稳定性:稳定
地精排序(Gnome Sort),被Dick Grune称为最简单的排序算法。整个算法只有一层循环,默认情况下前进冒泡,一旦遇到冒泡的情况发生就往回冒,直到把这个数字放好,然后继续前进,前进到数组最后一个数结束。此排序算法虽然代码极短,但效率不高。
算法示意图:
实现代码:
- void GnomeSort(int *a, int len)
- {
- int i=0;
- while (i<len)
- {
- if (i==0 || a[i-1]<=a[i]){
- i++;
- }
- else {
- swap(a[i], a[i-1]);
- i--;
- }
- }
- }
(五)快速排序
最差时间复杂度:O(n^2)
最优时间复杂度:O(nlogn)
平均时间复杂度:O(nlogn)
稳定性:不稳定
快速排序(Quick Sort),使用分治法策略来把一个串行分为两个子串行,左边子串的值总小于右边的子串。此算法的三个步骤:
1.分解:将数组A[l...r]划分成两个(可能空)子数组A[l...p-1]和A[p+1...r],使得A[l...p-1]中的每个元素都小于等于A(p),而且,小于等于A[p+1...r]中的元素。下标p也在这个划分过程中计算。
2.解决:通过递归调用快速排序,对数组A[l...p-1]和A[p+1...r]排序。
3.合并:因为两个子数组时就地排序,将它们的合并并不需要操作,整个数组A[l..r]已经排序。
算法示意图(关键字的处理与下面代码实现略有不同):
实现代码(其他实现方法见“三种快速排序算法的实现”):
- int partition(int* a, int left, int right)
- {
- int x = a[right];
- int i = left-1, j = right;
- for (;;)
- {
- while(a[++i] < x) { }
- while(a[--j] > x) { if(j==left) break;}
- if(i < j)
- swap(a[i], a[j]);
- else break;
- }
- swap(a[i],a[right]);
- return i;
- }
- void quickSort(int* a, int left, int right)
- {
- if (left<right)
- {
- int p = partition(a, left, right);
- quickSort(a, left, p-1);
- quickSort(a, p+1, right);
- }
- }
(六)臭皮匠排序
最差时间复杂度:O(n^2.7)
臭皮匠排序(Stooge Sort),是一种低效的排序算法,在《算法导论》第二版第7章的思考题中被提到,是由Howard Fine等教授提出的所谓“漂亮的”排序算法。将数列平分为三个子串,依次递归排序前两个子串、后两个子串、前两个子串,最后确保整个数列有序。此算法在最坏情况下的递归式为T(n) = 3T(2n/3) + 1。由主定理很容易知道它的算法复杂性为:T(n)
= O(n^log(3/2, 3))。很显然log(3/2, 3))>2,也就是说这个算法比插入排序的O(n^2)性能还差。
实现代码:
- void StoogeSort(int *a, int i, int j)
- {
- if(a[i]>a[j])
- swap(a[i], a[j]);
- if((i+1)>=j)
- return;
- int k = (j-i+1)/3;
- StoogeSort(a, i, j-k);
- StoogeSort(a, i+k, j);
- StoogeSort(a, i, j-k);
- }