【排序算法】用C++实现各种排序算法
1. 在平时的学习中,很经常听到各种排序算法,其各有优缺点。尝试自己用C++实现各排序算法,作为对算法的基础学习。
常见的内部排序算法:
- 冒泡排序
- 选择排序
- 插入排序
- 归并排序
- 快速排序
- 堆排序
- 希尔排序
- 基数排序
2. 各种排序算法的思想及其C++实现(以需排列元素有n个,目标为得到从小到大的序列为例)
2.1 冒泡排序
冒泡排序是我接触到的第一个排序算法(高中参加计算机竞赛时学过),其算法思想相对简单,用代码实现起来也较简单。即每次通过不断比较相邻的两个值,将最大值移至末尾:先比较第一个和第二个数,若第二个数较小,则交换两个数,将两数中的较大者移到第二个;再比较第二个和第三个,将两数中的较大者移到第三个;...;最后即可将整列数中的最大值移到最末尾。上述过程只能将一个数移到正确位置,循环n-1次后,即可全部排序完成,得到从小到大的序列。
C++代码:(使用Visual Studio 2013为IDE,文章中之后的代码将只包含排序函数部分)
1 // BubbleSort.cpp : 定义控制台应用程序的入口点。
2 //
3
4 #include "stdafx.h"
5 #include <iostream>
6
7 using namespace std;
8
9 void BubbleSort(int[], int);
10
11 int _tmain(int argc, _TCHAR* argv[])
12 {
13 int array[] = { 34, 65, 12, 43, 67, 5, 78, 43, 3, 70 };
14 int length = sizeof(array) / sizeof(int);
15 for (int i = 0; i < length - 1; i++)
16 cout << array[i] << " ";
17 cout << array[length - 1] << endl;
18 cout << "length=" << length << endl;
19
20 BubbleSort(array, length);
21
22 for (int i = 0; i < length - 1; i++)
23 cout << array[i] << " ";
24 cout << array[length - 1] << endl;
25 cout << "length=" << length << endl;
26 system("pause");
27 return 0;
28 }
29
30 void BubbleSort(int a[], int length)
31 {
32 for (int i = 1; i < length; i++) //i等于1到9,指循环的次数
33 for (int j = 0; j < length - 1; j++)
34 if (a[j] > a[j + 1])
35 swap(a[j], a[j + 1]);
36 }
2.2 选择排序
选择排序与冒泡排序的思想有些相似:它通过依次将第1个数与后续所有数比较,发现比第一个数小的数,就将其与第一个数交换,从而使第一个数为所有数中的最小值;再将第二个数依次与后续所有数比较,从而将次小的数移至序列第二个;... ;n-1次操作后,即完成n个数从小到大的排序。
C++代码:
1 void SelectionSort(int a[], int length)
2 {
3 for (int i = 0; i < length - 1; i++)
4 for (int j = i + 1; j < length; j++)
5 if (a[i] > a[j])
6 swap(a[i], a[j]);
7 }
2.3 插入排序
插入排序默认第一项是排好序的(因为只有一项,不会是乱序的),然后将第二项插入到这个排好序的序列中,然后是第三项,第四项,...,第n项。即完成排序。
C++代码:(这里采用了二分查找)
1 template<class Type>
2 void InsertionSort(Type* array, int start, int end)
3 {
4 int flag = start + 1; //flag之前是排好序的
5 for (flag = start + 1; flag <= end; flag++)
6 {
7 int temp = array[flag];
8 int left = start, right = flag -1;
9 int mid = (left + right) / 2;
10
11 if (temp < array[left])
12 {
13 for (int i = right + 1; i > left; i--)
14 {
15 array[i] = array[i - 1];
16 }
17 array[left] = temp;
18 continue;
19 }
20 if (temp >= array[right])
21 {
22 continue;
23 }
24
25 while (array[mid] != temp && (right - left) > 1)
26 {
27 if (temp < array[mid])
28 {
29 right = mid;
30 mid = (left + right) / 2;
31 }
32 if (temp > array[mid])
33 {
34 left = mid;
35 mid = (left + right) / 2;
36 }
37 }
38 while (array[mid] <= temp)
39 mid++;
40 for (int i = right + 1; i > mid; i--)
41 {
42 array[i] = array[i - 1];
43 }
44 array[mid] = temp;
45 }
46 }
2.4 归并排序
归并排序先将一列无序的数分成两列无序的数,再将两列无序数分成四列...直到分成的每列数只含一个数。这些只含一个数的“无序的一列数”其实已经是“有序的”,因为只有一个数,没有“乱序之说”。再将这些“有序的列”逆向两两归并,得到长度更长的有序的列,直到将所有的项归并为一列有序的列。即完成排序。
C++代码:
1 template<class temp>
2 void MergeSort(temp * a, int begin, int end) //对数列a[]从begin到end之间的元素进行排序。
3 {
4 if (end - begin == 1) //若只有两个元素,交换即可。
5 if (a[begin] > a[end])
6 swap(a[begin], a[end]);
7
8 if (end - begin > 1) //大于两个元素使用归并排序,这是个递归的过程。
9 {
10 int mid = (begin + end) / 2;
11 MergeSort(a, begin, mid);
12 MergeSort(a, mid + 1, end);
13 Merge(a, begin, mid, end);
14 }
15 }
16
17 //数列a[]从begin到mid,以及从mid + 1到end,是已经排好序的
18 //现在将两部分归并成一个排好序的数列
19 template<class temp>
20 void Merge(temp*a, int begin, int mid, int end)
21 {
22 temp * tempArray = new temp[end - begin + 1]; //暂存排序的结果。
23 bool * IsMerged = new bool[end - begin + 1]; //标记元素是否已经被归并到tempArray中。
24 for (int i = 0; i < end - begin + 1; i++){
25 tempArray[i] = 0;
26 IsMerged[i] = false;
27 }
28
29 int indexA = begin; //第一部分第一个元素。
30 int indexB = mid + 1; //第二部分第一个元素。
31 int index = 0; //用以插入到tempArray。
32 while (indexA <= mid && indexB <= end)
33 {
34 if (a[indexA] <= a[indexB])
35 {
36 tempArray[index] = a[indexA];
37 index++;
38 IsMerged[indexA - begin] = true;
39 indexA++;
40 }
41 else
42 {
43 tempArray[index] = a[indexB];
44 index++;
45 IsMerged[indexB - begin] = true;
46 indexB++;
47 }
48 }
49
50 //将剩下的元素插入到tempArray
51 for (int i = 0; i < end - begin + 1; i++)
52 if (IsMerged[i] == false)
53 tempArray[index++] = a[begin + i];
54
55 for (int i = 0; i < end - begin + 1; i++)
56 a[begin + i] = tempArray[i];
57 }
归并排序的主要部分在于Merge函数的实现,即将两列有序数合并成一列。
2.5 快速排序
快速排序被认为是最好的排序算法之一。快速排序经过一轮比较,将比某一个数小的数全都至于这个数前面,把比这个数大的数放在都放在它的后面。这样就使得这个数位于了它的正确位置,之后的排序工作都不会再影响到它;同时它也把整列数分成了两部分,再分别对剩下的两部分做相同的操作...直到所有数位于它的正确位置,即完成了排序。
C++代码:
1 template<class temp>
2 void QuickSort(temp * array, int begin, int end)
3 {
4 if (end - begin == 1) //如果只有两个元素,直接交换即排好序
5 if (array[begin] > array[end])
6 swap(array[begin], array[end]);
7
8 if (end - begin > 1)
9 {
10 temp x = array[begin];
11 int left = begin;
12 int right = end;
13 while (right > left)
14 {
15 while (right > left && array[right] >= x ) //从右向左找出比x小的数
16 right--;
17 if (right > left)
18 {
19 array[left] = array[right];
20 left++;
21 }
22
23 while (right > left && array[left] < x) //从左向右找出比x大或等于x的数
24 left++;
25 if (right > left)
26 {
27 array[right] = array[left];
28 right--;
29 }
30 }
31 array[right] = x;
32 QuickSort(array, begin, right - 1);
33 QuickSort(array, right + 1, end);
34 }
35 }
2.6 堆排序
堆排序也是一种时间复杂度较低的排序算法。它通过建立一棵完全二叉树,使数组中每个元素对应其中一个节点;再将这课完全二叉树建成堆:使其中每个根节点都大于(大顶堆)或小于(小顶堆)它的左右孩子节点,这样堆顶元素就是所有节点中最大(大顶堆)或最小的(小顶堆)。通过不断取出堆顶元素,并将剩余元素重新调整成堆,即可完成排序。
C++代码:
1 //堆排序
2 template <typename T>
3 void HeapSort(T*array, int size)
4 {
5 for (int i = size - 1; i >= 0; i--) //建大顶堆
6 MaxHeapify(array, size, i);
7
8 while (size > 1) //每次取堆顶元素(最大值),再将剩余元素重新调整成大顶堆,再取剩余元素的堆顶元素(最大值)
9 {
10 swap(array[0], array[size - 1]);
11 size--;
12 if (size > 1)
13 MaxHeapify(array, size, 0);
14 }
15
16 return;
17 }
18
19 //调整以element元素为根节点的树
20 //使其成为大顶堆
21 template <typename T>
22 void MaxHeapify(T*array, int size, int element) //element为当前调整的树的根节点
23 {
24 int lchild = element * 2 + 1;
25 int rchild = lchild + 1;
26 while (rchild < size) //element既有左子树又有右子树
27 {
28 if (array[element] >= array[lchild] && array[element] >= array[rchild]) //根节点最大,已经完成此次调整,直接返回
29 return;
30
31 if (array[lchild] >= array[rchild]) //左孩子最大
32 {
33 swap(array[lchild], array[element]);
34 element = lchild; //经过调整后,左子树可能不再符合大顶堆,所以循环调整左子树
35 }
36 else
37 {
38 swap(array[rchild], array[element]);
39 element = rchild; //经过调整后,右子树可能不再符合大顶堆,所以循环调整右子树
40 }
41
42 lchild = element * 2 + 1; //重置左右子树
43 rchild = lchild + 1;
44 }
45
46 if (lchild == size - 1 && array[lchild] > array[element]) //只有左子树且左子树大于根节点
47 swap(array[lchild], array[element]);
48
49 return;
50 }
堆排序的关键在于将二叉树调整为堆的函数。
2.7 希尔排序
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。(来自百度百科:希尔排序)
也就是说,希尔排序是插入排序的改进,它通过先将整体分组,然后对分组进行排序;再不断减少分组的个数,直至分组个数为1(也就是整体进行排序),排序完成。
C++代码1:
1 template <typename T>
2 void ShellSort(T* array, int length)
3 {
4 for (int gap = length / 2; gap >= 1; gap /= 2) //不同的增量
5 for (int i = 0; i < gap; i++)
6 for (int j = i; j < length; j += gap)
7 if (j + gap < length && array[j] > array[j + gap])
8 {
9 int temp = array[j + gap];
10 int k = j;
11 while (array[k] > temp && k >= 0)
12 {
13 array[k + gap] = array[k];
14 k -= gap;
15 }
16 array[k + gap] = temp;
17 }
18 }
更简洁的实现,C++代码2:
1 template <typename T>
2 void ShellSort(T* array, int length)
3 {
4 for (int gap = length / 2; gap >= 1; gap /= 2)
5 for (int i = 0; i < gap; i++)
6 for (int j = i; j < length - gap; j += gap)
7 for (int k = j; array[k] > array[k + gap] && k >= 0; k -= gap)
8 swap(array[k], array[k + gap]);
9 }
2.8 基数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。(来自百度百科:基数排序)
基数排序的思想在日常生活中很常见,比如两数比大小时,先比较最高位、再比较次高位...基数排序则是先对个位排序、再对十位排序、...、直至最高位(或者从最高位开始到最低位)。
C++代码:
1 template <typename T>
2 void RadixSort(T* array, int length)
3 {
4 int digits_of_max_number = Digits_of_max_number(array, length); //最大项的位数,也就是排序循环的次数
5 T* temp = new T[length]; //临时储存数组
6 int count[10];
7 int index = 1; //先对个位进行排序
8 for (int i = 0; i < digits_of_max_number; i++) //循环digits_of_max_number次,i=0代表个位
9 {
10
11 for (int j = 0; j < 10; j++) //每次循环前清零
12 count[j] = 0;
13 for (int j = 0; j < length; j++) //计算从右往左第i位(i=0代表个位)中k出现的次数count[k]
14 {
15 int k = (array[j] / index) % 10;
16 count[k]++;
17 }
18
19 for (int j = 1; j < 10; j++) //通过上一个循环得到的count[k],计算“从右往左第i位(i=0代表个位)为k的数”本次循环后在新数组中应处位置的下标
20 count[j] = count[j - 1] + count[j];
21
22 for (int j = length - 1; j >= 0; j--) //将array[j]通过count[k]调整到新位置,暂存到数组temp中
23 {
24 int k = (array[j] / index) % 10;
25 temp[count[k] - 1] = array[j];
26 count[k]--;
27 }
28 for (int j = 0; j < length; j++) //将temp中的数复制回array
29 array[j] = temp[j];
30
31 index *= 10; //对下一位进行排序
32 }
33
34 return;
35 }
36
37 template <typename T>
38 int Digits_of_max_number(T* array, int length)
39 {
40 int d = 1, index = 10;
41 for (int i = 0; i < length; i++)
42 while (array[i] >= index)
43 {
44 d++;
45 index *= 10;
46 }
47 return d;
48 }