排序就是将一组数据按一定规律排列起来,它在计算机操作中被频繁调用,因而是软件设计中最为基础的算法之一。本文所述算法均以升序排序,且使用以下公共函数。
#include <stdio.h> #include <iostream> #include <stdlib.h> #include <time.h> using namespace std;
#define LEN (10) // general function template <class T> void Display(T a[], int len) { for (int i = 0; i < len; i++) { cout << a[i] << '/t'; } cout << endl; } template <class T> inline void Swap(T& a, T& b) { T t = a; a = b; b = t; }
int main(int argc, char* argv[]) { // initialization int ArrInput[LEN]; srand((unsigned)time(NULL)); // seed the random generator with current time for (int i = 0; i < LEN; i++) { ArrInput[i] = rand(); } // sort BubbleSort(ArrInput, LEN); // or other sort algorithm // display Display(ArrInput, LEN); return 0; }
1、冒泡排序:
冒泡排序(Bubble Sort)对一N长数列作N-1次扫描,每次扫描时都从前往后依次比较两相邻元素的大小,并在发现逆序时作交换操作。一个广泛使用的加速技巧是:在某次扫描完成后,发现本次扫描未进行任何交换操作时,算法终止。
班里要参加广播操比赛(初高中时最烦做广播操,现在却有些怀念),站队完毕后,老师总会不断巡视整个队列,比较挨着的某两人的身高,指挥某两位对换一下位置(总会有人在此时遭受无情打击)。只是与计算机不同,老师有时会犯错,可能又会接着指挥:你们俩还是换回来吧(某人窃喜:就是嘛,我怎么可能比他矮!);计算机不会犯这种错误。
与日常生活类似,当给出的数列已经比较有序,只需要小幅调整时,可考虑使用冒泡排序。
template <class T> void Bubble(T a[], int len) { // bubble the largest number to the end of the array for(int i = 0; i < len-1; i++) { if(a[i] > a[i+1]) { Swap(a[i], a[i+1]); } } } template <class T> void BubbleSort(T a[], int len) { for(int i = len; i > 1; i--) { Bubble(a, i); } }
// accelerated version template <class T> void BubbleSort(T a[], int len) { int nLoopCnt = 0; // scans counter for demonstration bool bSorted = false; // flag to accelerate algorithm for(int nSize = len; nSize > 1; nSize--) { bSorted = true; for (int i = 0; i < nSize - 1; i++) { // this loop is known as 'bubble' if(a[i] > a[i+1]) { Swap(a[i], a[i+1]); bSorted = false; } } nLoopCnt++; if (true == bSorted) { break; } } cout << "Completed after " << nLoopCnt << " scan(s)." << endl; }
2、选择排序:
选择排序(Selection Sort)从N长数列中找出最大数,将其交换到N长数列最后;接着从余下的N-1长数列中再找出最大数,将其交换到N-1长数列最后;……;不断重复下去,直至最后剩余数列长度为1为止。
在男生宿舍的卧谈会,为班中女生选美时,会自然而然地使用选择排序。
与日常生活类似,当急切地渴望知道一个队列中的前几名时,可以使用选择排序。
template<class T> int Max(T a[], int len) { int pos = 0; for(int i = 1; i < len; i++) { if(a[pos] < a[i]) { pos = i; } } return pos; } template <class T> void SelectionSort(T a[], int len) { for(int i = len; i > 1; i--) { int j = Max(a, i); Swap(a[j], a[i-1]); // swap the largest number to the end of the array } }
// accelerated version template <class T> void SelectionSort(T a[], int len) { int nLoopCnt = 0; // scans counter for demonstration bool bSorted = false; // flag to accelerate algorithm for(int nSize = len; nSize > 1; nSize--) { bSorted = true; int pos = 0; for (int i = 1; i < nSize; i++) { if(a[pos] < a[i]) { pos = i; } else { bSorted = false; } } if (true == bSorted) { break; } nLoopCnt++; Swap(a[pos], a[nSize-1]); } cout << "Completed after " << nLoopCnt << " scan(s)." << endl; }
3、插入排序:
插入排序(Insertion Sort)从N长数列中依次取出所有数,按大小关系依次插入到一新数列中,并始终保持新数列有序。
打牌时,洗完牌后,将桌上的牌(一个N长无序数列)不断拿入手中(手中的牌就是一个有序数列)就是一个插入排序的过程。
与日常生活类似,当我们会不断收到新数据并想始终保持已有数据的有序状态时,可优先考虑使用插入排序。
template<class T> void Insert(T a[], int len, const T& x) { // insert 'x' to the ascending array 'a' and keep ascending for (int i = len-1; i >= 0 && x < a[i]; i--) { a[i+1] = a[i]; // asserted 'x' has enough memory } a[i+1] = x; } template<class T> void InsertionSort(T a[], int len) { for (int i = 1; i < len; i++) { T t = a[i]; Insert(a, i, t); } }
4、计数排序:
计数排序(Counting Sort)首先为N长数列中的每个元素统计出其等级(或称为rank,可定义为数列中所有比它小的元素数目加上在它左边出现的与它相同的元素数目),并按每个元素的等级将其放入一个有序数列中。
由算法原理可以看出,计数排序一般适用于数组操作。虽然在人们的日常习惯中,不大容易找到计数排序的应用实例,但它有一个非常适合实现各类科学算法的先天优势,它可以返回数列在排序后的索引(类似于Matlab的sort命令,这在写某些领域的特定算法时非常有用,本人在编写ViV时使用的就是计数排序)。
template <class T> void Rank(T a[], int len, int r[]) { for(int i = 0; i < len; i++) { // initialize the rank array r[i] = 0; } for (i = 1; i < len; i++) { for (int j = 0; j < i; j++) { if (a[j] <= a[i]) { r[i]++; } else { r[j]++; } } } } template <class T> void CountingSort(T a[], int len, int r[]) { Rank(a, len, r); T *u = new T[len]; for(int i = 0; i < len; i++) { // need extra memory u[r[i]] = a[i]; } for (i = 0; i < len; i++) { // copy back a[i] = u[i]; } delete []u; }
// a modified version that need no extra memory template <class T> void CountingSort(T a[], int len, int r[]) { Rank(a, len, r); for (int i = 0; i < len; i++) { while (i != r[i]) { // need no extra memory int t = r[i]; Swap(a[i], a[t]); // swap the a[i] to the right position Swap(r[i], r[t]); // correct 'r' corresponding to the change of 'a' } } }
以上4种排序算法最容易理解,但时间复杂度最次,均为O(n2)。由于变种繁多,且无太大实际意义,详细的空间复杂度分析与时间复杂度分析在此不作展开。在实际应用中,4种排序算法的效率并不相同;实践证明,插入排序是相对最快的