1.冒泡排序
2.选择排序
3.插入排序
4.交换排序
5.归并排序
6.快速排序
7.堆排序
8.希尔排序
9.双向冒泡排序
10.计数排序
11.基数排序
一、简单排序算法
1.冒泡法:
这是最原始,也是众所周知的最慢的算法了。他的名字的由来因为它的工作看来象是冒泡:
-
-
- void BubbleSort(int* pData,int Count)
- {
- int iTemp;
- for(int i=1; i < Count; i++)
- {
- for(int j = Count-1; j >= i; j--)
- {
- if(pData[j]<pData[j-1])
- {
- iTemp = pData[j-1];
- pData[j-1] = pData[j];
- pData[j] = iTemp;
- }
- }
- }
- }
倒序(最糟情况)
第一轮:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交换3次)
第二轮:7,10,9,8->7,10,8,9->7,8,10,9(交换2次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:6次
其他:
第一轮:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交换2次)
第二轮:7,8,10,9->7,8,10,9->7,8,10,9(交换0次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:3次
上面我们给出了程序段,现在我们分析它:这里,影响我们算法性能的主要部分是循环和交换,
显然,次数越多,性能就越差。从上面的程序我们可以看出循环的次数是固定的,为1+2+...+n-1。
写成公式就是1/2*(n-1)*n。
现在注意,我们给出O方法的定义:
若存在一常量K和起点n0,使当n>=n0时,有f(n)<=K*g(n),则f(n) = O(g(n))。
从程序后面所跟的表可以看到,两种情况的循环相同,交换不同。其实交换本身同数据源的
有序程度有极大的关系,当数据处于倒序的情况时,交换次数同循环一样(每次循环判断都会交换),
复杂度为O(n*n)。当数据为正序,将不会有交换。复杂度为O(0)。乱序时处于中间状态。正是由于这样的
原因,我们通常都是通过循环次数来对比算法。
2.交换法:
交换法的程序最清晰简单,每次用当前的元素一一的同其后的元素比较并交换。
-
-
- void ExchangeSort(int* pData,int Count)
- {
- int iTemp;
- for(int i=0;i<Count-1;i++)
- {
- for(int j=i+1;j<Count;j++)
- {
- if(pData[j]<pData[i])
- {
- iTemp = pData[i];
- pData[i] = pData[j];
- pData[j] = iTemp;
- }
- }
- }
- }
倒序(最糟情况)
第一轮:10,9,8,7->9,10,8,7->8,10,9,7->7,10,9,8(交换3次)
第二轮:7,10,9,8->7,9,10,8->7,8,10,9(交换2次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:6次
其他:
第一轮:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交换1次)
第二轮:7,10,8,9->7,8,10,9->7,8,10,9(交换1次)
第一轮:7,8,10,9->7,8,9,10(交换1次)
循环次数:6次
交换次数:3次
从运行的表格来看,交换几乎和冒泡一样糟。事实确实如此。循环次数和冒泡一样
也是1/2*(n-1)*n,所以算法的复杂度仍然是O(n*n)。由于我们无法给出所有的情况,所以
只能直接告诉大家他们在交换上面也是一样的糟糕(在某些情况下稍好,在某些情况下稍差)。
3.选择法:
现在我们终于可以看到一点希望:选择法,这种方法提高了一点性能(某些情况下)
这种方法类似我们人为的排序习惯:从数据中选择最小的同第一个值交换,在从剩下的部分中
选择最小的与第二个交换,这样往复下去。
-
-
- void SelectSort(int* pData,int Count)
- {
- int iTemp;
- int iPos;
- for(int i=0;i<Count-1;i++)
- {
- iTemp = pData[i];
- iPos = i;
- for(int j=i+1;j<Count;j++)
- {
- if(pData[j]<iTemp)
- {
- iTemp = pData[j];
- iPos = j;
- }
- }
- pData[iPos] = pData[i];
- pData[i] = iTemp;
- }
- }
倒序(最糟情况)
第一轮:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7->(iTemp=7)7,9,8,10(交换1次)
第二轮:7,9,8,10->7,9,8,10(iTemp=8)->(iTemp=8)7,8,9,10(交换1次)
第一轮:7,8,9,10->(iTemp=9)7,8,9,10(交换0次)
循环次数:6次
交换次数:2次
其他:
第一轮:8,10,7,9->(iTemp=8)8,10,7,9->(iTemp=7)8,10,7,9->(iTemp=7)7,10,8,9(交换1次)
第二轮:7,10,8,9->(iTemp=8)7,10,8,9->(iTemp=8)7,8,10,9(交换1次)
第一轮:7,8,10,9->(iTemp=9)7,8,9,10(交换1次)
循环次数:6次
交换次数:3次
遗憾的是算法需要的循环次数依然是1/2*(n-1)*n。
所以算法复杂度为O(n*n)。
我们来看他的交换。由于每次外层循环只产生一次交换(只有一个最小值)。所以f(n)<=n
所以我们有f(n)=O(n)。所以,在数据较乱的时候,可以减少一定的交换次数。
4.插入法:
插入法较为复杂,它的基本工作原理是抽出牌,在前面的牌中寻找相应的位置插入,然后继续下一张
-
-
-
-
-
-
-
- void InsertSort(int* pData,int Count)
- {
- int iTemp;
- int iPos;
- for(int i=1;i<Count;i++)
- {
- iTemp = pData[i];
- iPos = i-1;
- while((iPos>=0) && (iTemp<pData[iPos]))
- {
- pData[iPos+1] = pData[iPos];
- iPos--;
- }
- pData[iPos+1] = iTemp;
- }
- }
倒序(最糟情况)
第一轮:10,9,8,7->9,10,8,7(交换1次)(循环1次)
第二轮:9,10,8,7->8,9,10,7(交换1次)(循环2次)
第一轮:8,9,10,7->7,8,9,10(交换1次)(循环3次)
循环次数:6次
交换次数:3次
其他:
第一轮:8,10,7,9->8,10,7,9(交换0次)(循环1次)
第二轮:8,10,7,9->7,8,10,9(交换1次)(循环2次)
第一轮:7,8,10,9->7,8,9,10(交换1次)(循环1次)
循环次数:4次
交换次数:2次
上面结尾的行为分析事实上造成了一种假象,让我们认为这种算法是简单算法中最好的,其实不是,
因为其循环次数虽然并不固定,我们仍可以使用O方法。从上面的结果可以看出,循环的次数f(n)<=
1/2*n*(n-1)<=1/2*n*n。所以其复杂度仍为O(n*n)(这里说明一下,其实如果不是为了展示这些简单
排序的不同,交换次数仍然可以这样推导)。现在看交换,从外观上看,交换次数是O(n)(推导类似
选择法),但我们每次要进行与内层循环相同次数的‘=’操作。正常的一次交换我们需要三次‘=’
而这里显然多了一些,所以我们浪费了时间。
二、高级排序算法:
5、归并排序:
归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
归并排序具体工作原理如下(假设序列共有n个元素):
- 将序列每相邻两个数字进行归并操作,形成floor(n / 2)个序列,排序后每个序列包含两个元素
- 将上述序列再次归并,形成floor(n / 4)个序列,每个序列包含四个元素
- 重复步骤2,直到所有元素排序完毕
-
- void Merge(int* pData, int first, int mid, int last)
- {
-
- int *pTemp = new int[last-first+1];
- int begin1 = first, begin2 = mid + 1;
- int i;
- for(i = 0; begin1 <= mid && begin2 <= last; ++i)
- {
-
- if(pData[begin1] <= pData[begin2])
- pTemp[i] = pData[begin1++];
- else
- pTemp[i] = pData[begin2++];
- }
-
- if(begin1 <= mid)
- memcpy_s(pTemp + i, (last-first+1)*sizeof(int), pData + begin1, (mid-begin1+1)*sizeof(int));
- else if(begin2 <= last)
- memcpy_s(pTemp + i, (last-first+1)*sizeof(int), pData + begin2, (last-begin2+1)*sizeof(int));
-
- memcpy_s(pData + first, (last-first+1)*sizeof(int), pTemp, (last-first+1)*sizeof(int));
- delete pTemp;
- }
-
-
- void MergeSort(int* pData, int first, int last)
- {
- int mid = 0;
- if(first < last)
- {
- mid = (first+last)/2;
- MergeSort(pData, first, mid);
- MergeSort(pData, mid+1, last);
- Merge(pData, first, mid, last);
- }
- }
比较操作的次数介于(nlogn) / 2和nlogn − n + 1。 赋值操作的次数是(2nlogn)。 归并算法的空间复杂度为:Θ (n)
6、快速排序:
首先我们选择一个中间值middle程序中我们使用数组中间值,然后
把比它小的放在左边,大的放在右边(具体的实现是从两边找,找到一对后交换)。然后对两边分别使
用这个过程(最容易的方法——递归)。
- #include <utility>
-
-
- void QuickSort1(int* pData, int left, int right)
- {
- int i,j;
- int middle,iTemp;
- i = left;
- j = right;
- middle = pData[(left+right)/2];
- do
- {
- while((pData[i]<middle) && (i<right))
- i++;
- while((pData[j]>middle) && (j>left))
- j--;
- if(i<=j)
- {
-
- std::swap(pData[i], pData[j]);
- i++;
- j--;
- }
- }while(i<=j);
-
- if(left<j)
- QuickSort1(pData,left,j);
-
- if(right>i)
- QuickSort1(pData,i,right);
- }
-
-
-
-
- void QSort(int* pData, int nCount, int m = -1)
- {
- static const int M=7, NSTACK=64;
- int i,ir,j,jstack=-1,l=0;
- int a;
- int istack[NSTACK];
- if (m > 0) nCount = min(m,nCount);
- ir=nCount-1;
- for (;;)
- {
- if (ir-l < M)
- {
- for (j=l+1;j<=ir;j++)
- {
- a=pData[j];
- for (i=j-1;i>=l;i--)
- {
- if (pData[i] <= a) break;
- pData[i+1]=pData[i];
- }
- pData[i+1]=a;
- }
- if (jstack < 0) break;
-
- ir=istack[jstack--];
- l=istack[jstack--];
- }
- else
- {
- int k=(l+ir) >> 1;
- std::swap(pData[k],pData[l+1]);
-
-
- if (pData[l] > pData[ir])
- {
- std::swap(pData[l],pData[ir]);
- }
- if (pData[l+1] > pData[ir])
- {
- std::swap(pData[l+1],pData[ir]);
- }
- if (pData[l] > pData[l+1])
- {
- std::swap(pData[l],pData[l+1]);
- }
- i=l+1;
- j=ir;
- a=pData[l+1];
- for (;;)
- {
- do i++; while (pData[i] < a);
- do j--; while (pData[j] > a);
- if (j < i) break;
- std::swap(pData[i],pData[j]);
- }
- pData[l+1]=pData[j];
- pData[j]=a;
-
- jstack += 2;
-
- if (jstack >= NSTACK) throw("NSTACK too small in sort.");
- if (ir-i+1 >= j-l)
- {
- istack[jstack]=ir;
- istack[jstack-1]=i;
- ir=j-1;
- }
- else
- {
- istack[jstack]=j-1;
- istack[jstack-1]=l;
- l=i;
- }
- }
- }
- }
这里我没有给出行为的分析,因为这个很简单,我们直接来分析算法:首先我们考虑最理想的情况
1.数组的大小是2的幂,这样分下去始终可以被2整除。假设为2的k次方,即k=log2(n)。
2.每次我们选择的值刚好是中间值,这样,数组才可以被等分。
第一层递归,循环n次,第二层循环2*(n/2)......
所以共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+n=k*n=log2(n)*n
所以算法复杂度为O(log2(n)*n)
其他的情况只会比这种情况差,最差的情况是每次选择到的middle都是最小值或最大值,那么他将变
成交换法(由于使用了递归,情况更糟)。
第二种方法采用了一些优化方法,效率要更高,避免了出现最坏的情况 。
如果你担心这个问题,你可以使用堆排序,这是一种稳定的O(log2(n)*n)算法,但是通常情况下速度要慢
于快速排序(因为要重组堆)。
7、堆排序
堆排序的完整实现。代表了一个大公司的整个生存循环:雇佣N/2个工人,雇佣N/2个潜在的管理者,有一个值为升迁的过程,它是一种超级Peter原理的排序;最后按注定的过程:每个原来的雇员被一次提升为总裁。
堆排序的过程是:
- 创建一个堆H[0..n-1]
- 把堆首(最大值)和堆尾互换
- 把堆的尺寸缩小1,并调用
shift_down
,目的是把新的数组顶端数据调整到相应位置,即重建堆
- 重复步骤2,直到堆的尺寸为1
-
-
-
-
-
-
-
-
-
-
- void shift_down(int* pData, int l, int r)
- {
- int j, jold;
- int a = pData[l];
- jold = l;
- j = 2 * l + 1;
- while (j <= r)
- {
-
- if(j < r && pData[j] < pData[j+1]) j++;
-
- if (a >= pData[j]) break;
-
- pData[jold] = pData[j];
- jold = j;
- j = 2 * j + 1;
- }
- pData[jold] = a;
- }
-
- void HeapSort(int* pData, int nCount)
- {
-
- for(int i = nCount/2-1; i >= 0; i--)
-
- shift_down(pData, i, nCount-1);
-
- for(int i = nCount-1; i > 0; i--)
- {
-
- std::swap(pData[0], pData[i]);
- shift_down(pData, 0, i-1);
- }
- }
堆排序的平均时间复杂度为O(nlogn),空间复杂度为Θ(1)。
8、SHELL排序
-
-
- void ShellSort(int* pData, int nCount, int m)
- {
- int i, j, inc, n = nCount;
- int v;
- if (m > 0) n = min(m, n);
- inc = 1;
- do
- {
- inc *= 3;
- inc++;
- } while (inc <= n);
- do
- {
-
- inc /= 3;
- for (i = inc; i < n; i++)
- {
- v = pData[i];
- j = i;
- while (pData[j - inc] > v)
- {
- pData[j] = pData[j - inc];
- j -= inc;
- if (j < inc) break;
- }
- pData[j] = v;
- }
- } while (inc > 1);
- }
三、其他排序
9、双向冒泡: 又叫鸡尾酒排序
通常的冒泡是单向的,而这里是双向的,也就是说还要进行反向的工作。
代码看起来复杂,仔细理一下就明白了,是一个来回震荡的方式。
-
- void Bubble2Sort(int* pData, int nCount)
- {
- int i;
- int left = 1;
- int right =nCount -1;
- int t;
- do
- {
-
- for (i = right; i >= left; i--)
- {
- if(pData[i] < pData[i-1])
- {
- std::swap(pData[i], pData[i-1]);
- t = i;
- }
- }
- left = t+1;
-
- for(i = left; i < right + 1; i++)
- {
- if(pData[i] < pData[i-1])
- {
- std::swap(pData[i], pData[i-1]);
- t = i;
- }
- }
- right = t-1;
- }while(left <= right);
- }
双向冒泡排序最糟或是平均所花费的次数都是O(n2),但如果序列在一开始已经大部分排序过的话,会接近O(n)。
10、计数排序
算法的步骤如下:
- 找出待排序的数组中最大和最小的元素
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
-
- void CountSort(int* pData, int nCount)
- {
- int i, j;
-
- int minNum = pData[0], maxNum = pData[0];
- for(i = 1; i < nCount; i++)
- {
- if(pData[i] < minNum)
- minNum = pData[i];
- else if(pData[i] > maxNum)
- maxNum = pData[i];
- }
-
- int range = maxNum - minNum + 1;
- int *pTemp = new int[range];
- memset(pTemp, 0, range * sizeof(int));
-
- for(i = 0; i < nCount; ++i)
- {
- pTemp[pData[i]-minNum]++;
- }
-
- int k = 0;
- for(i = minNum; i <= maxNum; ++i)
- for(j = 0; j < pTemp[i - minNum]; ++j)
- pData[k++] = i;
- delete pTemp;
- }
当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。
11、基数排序
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零. 然后, 从最低位开始, 依次进行一次排序.这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列.
基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。
-
-
- void RadixSort(int* pData, int nCount, int maxDigits)
- {
- const int base = 10;
- int arCountSort[10];
- int* pTemp = new int[nCount];
- int i, j;
- int t = 1;
-
- for(i = 0; i < maxDigits; ++i)
- {
-
- memset(arCountSort, 0, sizeof(int) * 10);
- for(j = 0; j < nCount; ++j)
- {
- arCountSort[pData[j]/t%base]++;
- }
-
- for(j = 1; j < 10; ++j)
- {
- arCountSort[j] += arCountSort[j-1];
- }
-
-
- for(j = nCount-1; j >= 0; --j)
- {
- int splitDigit = pData[j]/t%base;
- pTemp[arCountSort[splitDigit] - 1] = pData[j];
- arCountSort[splitDigit]--;
- }
- memcpy_s(pData, sizeof(int) * nCount, pTemp, sizeof(int) * nCount);
- t *= base;
- }
- }
MSD算法代码:
-
- void RadixSort(int* pData, int index, int nCount, int maxDigits)
- {
- if(nCount <= 1 || maxDigits <= 0)
- return;
- const int base = 10;
- int arCountSort[base];
- int arCountSortTemp[base];
- int* pTemp = new int[nCount];
- int i;
- int t = (int)pow((float)base, maxDigits-1);
-
-
- memset(arCountSort, 0, sizeof(int) * base);
- for(i = index; i < index + nCount; ++i)
- {
- arCountSort[pData[i]/t%base]++;
- }
- memcpy_s(arCountSortTemp, sizeof(int)*base, arCountSort, sizeof(int)*base);
-
- for(i = 1; i < base; ++i)
- {
- arCountSort[i] += arCountSort[i-1];
- }
- for(i = 0; i < base; ++i)
- {
- arCountSort[i] -= arCountSortTemp[i];
- }
-
- for(i = index; i < index + nCount; ++i)
- {
- int splitDigit = pData[i]/t%base;
-
- pTemp[arCountSort[splitDigit]] = pData[i];
- arCountSort[splitDigit]++;
- }
- memcpy_s(pData + index, sizeof(int) * nCount, pTemp, sizeof(int) * nCount);
-
- RadixSort(pData, index, arCountSort[0], maxDigits-1);
- for(i = 1; i < base; ++i)
- {
- RadixSort(pData, index + arCountSort[i-1], arCountSort[i] - arCountSort[i-1], maxDigits-1);
- }
- delete pTemp;
- }
- void RadixSortMSD(int* pData, int nCount, int maxDigits)
- {
- RadixSort(pData, 0, nCount, maxDigits);
- printf_s("基数排序结果:");
- for(int i = 0; i < nCount; i++)
- {
- printf_s("%d ", pData[i]);
- }
- printf_s("/n");
- }