排序算法可以分为两类,低级排序算法和高级排序算法
低级排序算法:选择排序,冒泡排序,插入排序,希尔排序
高级排序算法:堆排序,归并排序,快速排序,排序树,基数排序
1 选择排序
1) 在对数组元素遍历的过程中,选择最大的元素放入末端的位置,或者选择最小的元素放入开头,不断压缩循环区间,直到只剩一个元素。放的动作通过交换来执行。
代码如下
函数主体由寻找最大元素的索引和交换元素组成
void selectionSort(int* array, int size)
{
for (int n = size; n > 1; n--)
{
int j = indexOfMax(array, n);
swap(array[j], array[n - 1]);
}
}
int indexOfMax(int* array, int size)
{
int result = 0;
for (int i = 1; i < size; ++i)
{
if (array[result] < array[i])
result = i;
}
return result;
}
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
2)及时终止的选择排序
为了防止对本身就已经有序的数组排序,需要设计及时终止的选择排序。
void selectionSortEffeic(int* array, int size)
{
bool sorted = false;
for (int n = size; n > 1&&!sorted; n--)
{
int indexOfMax = 0;
sorted = true;
for (int i =1; i < n; i++)
{
if (array[indexOfMax] < array[i])
indexOfMax = i;
else
sorted = false;
}
swap(array[indexOfMax], array[n - 1]);
}
}
2 冒泡排序
1) 在一次冒泡过程中,依次比较相邻元素,最终会把最大元素放在末尾。下一次冒泡过程在新的区间(去掉了最右边的元素)总进行。
代码如下
函数主体由n-1次冒泡组成
void bubbleSort(int* array, int size)
{
for (int n = size; n > 1; n--)
bubble(array, n);
}
void bubble(int* array, int size)
{
for (int i = 0; i < size-1; i++)
{
if(array[i]>array[i+1])
swap(array[i], array[i + 1]);
}
}
2)及时终止的冒泡排序
防止数组在有序的情况下继续冒泡
void bubbleSortEffeic(int* array, int size)
{
int sorted=false;
for (int n = size; n > 1&&!sorted; n--)
{
sorted = true;
for (int i = 0; i < n - 1; i++)
{
if (array[i] > array[i + 1])
{
sorted = false;
swap(array[i], array[i + 1]);
}
}
}
}
3 插入排序
将后面的元素插入前面的有序区间,直到有序区间扩展到原区间的大小。有序区间初始化为第一个元素。
代码如下
函数主体由n-1次插入构成
void insertionSort(int* array, int size)
{
for (int i = 1; i < size; i++)
{
int t = array[i];
insert(array, i, t);
}
}
void insert(int* array, int size, int x)
{
int i;
for (i = size - 1; i >= 0 && x < array[i]; i++)
{
array[i + 1] = array[i];
}
array[i + 1] = x;
}
4 希尔排序
希尔排序的基本思想是先将整个待排序列划分成为若干个子序列,分别对子序列进行排序,然后逐步缩小划分子序列的间隔,并重复上述操作,直到划分的间隔变为0。
代码如下:
void shellSort(int* array,int size)
{
int gap=size/2;
bool sorted=false;
while(gap>=1){
do{
sorted=true;
for(int i=0;i<size-gap;i++){
if(array[i]>array[i+gap]){
swap(array[i],array[i+gap]);
sorted=false;
}
}
}while(!sorted);
gap/=2;
}
}
5 堆排序
堆排序的基本思想建立在大根堆的基础上。先用一个数组初始化大根堆,然后从大根堆中提取最大元素放入数组的最右边位置,然后删除此最大元素重建大根堆,当然这里的删除不是真的删除元素本身,而是在下次建立大根堆的时候缩小数组范围,不要计入最大元素。重复此过程,直到大根堆中只剩下一个元素,此元素肯定是最小元素,排序完毕。
代码如下, 函数主体由大根堆的初始化和大根堆的调整两部分组成,核心代码是大根堆的调整函数,用到了二叉树的一些性质。
void heapSort(int* array,int size)
{
int root=(size-2)/2; //这里的根节点包含数组的索引为0的位置
for(;root>=0;root--){//大根堆初始化
heapAdjust(array,root,size);
}
int n=size;
while(n>1){ //大根堆调整
swap(array[0],array[n-1]);
--n;
heapAdjust(array,0,n);
}
}
void heapAdjust(int* array,int root,int size)
{
int child=2*root+1;
int rootElement=array[root];
while(child<size){
if(child<size-1&&array[child]<array[child+1]){
++child;
}
if(rootElement>=array[child]){ //符合大根堆的特性
break;}
array[(child-1)/2]=array[child]; //向上(往根节点方向)传递大的元素
child=2*child+1; //孩子向下延伸,中间的位置空着待定
}
array[(child-1)/2]=rootElement;
}
6 归并排序
1) 归并排序的基本思想是把待排元素序列分割成两个子序列(长度不一定相等,按中间值分割),然后给每一个子序列排序,然后再将它们合并成一个序列。
代码如下,函数是递归的形式,中间代码是两个有序数组的合并方法
void mergeSort(int* array,int left,int right)
{
if(left<right){//递归条件
int mid=(left+right)/2;
mergeSort(array,left,mid);
mergeSort(array,mid+1,right);
merge(array,left,mid,right);
}
}
void merge(int* array,int left,int mid,int right)
{
int len=right-left+1;
int* atemp=new int[len];
int i=0,j=mid+1,k=0;
while(i<=mid&&j<right){
if(array[i]<array[j]){
atemp[k++]=array[i++];
}else{
atemp[k++]=array[j++];
}
}
while(i<=mid){
atemp[k++]=array[i++];
}
while(j<=right){
atemp[k++]=array[j++];
}
copy(atemp,atemp+len,array+left);
delete [] atemp;
}
2)可以看到上述归并程序既有递归,又有元素复制,能不能消除复制呢。有一种迭代算法也可以进行归并排序,称之为直接归并排序,首先将两个相邻的大小为1的子序列归并,然后将每两个相邻大小为2的子序列归并,反复此过程直到只剩下一个有序序列。轮流地将元素从a归并到b,从b归并到a,实际上消除了从b到a的复制。
函数主体由log2(n)次归并构成,这里的归并过程比较特殊,在归并之前要保证归并的左右边界符合规范,也就是两个归并段的长度是一样的,如果出现了少于两个满数据段时要特殊处理。代码如下,
void mergeSort(int a[], int n)
{//直接归并排序,主要的优点是消除了从b到a的复制过程
int* b = new int[n];
int segmentSize = 1;
while (segmentSize < n)
{
mergePass(a, b, n, segmentSize);
segmentSize *= 2;
mergePass(b, a, n, segmentSize);
segmentSize *= 2;
}
delete[] b;
}
void mergePass(int x[], int y[], int n, int segmentSize)
{//将x归并到y
int i = 0;
while (i <= n - 2 * segmentSize){ //合并前的两段数据段都有segmenSize个元素
merge(x, y, i, i + segmentSize - 1, i + 2 * segmentSize - 1);
i = i + 2 * segmentSize;
}
if (i + segmentSize < n){//剩余两个数据段(这两个数据段其中一段不是满数据段)
merge(x, y, i, i + segmentSize - 1, n - 1);
}else{//只剩一个数据段,复制到y
copy(x + i, x + n, y + i);
}
}
void merge(int x[], int y[],int startOfFirst,int endOfFirst,int endOfSecond)
{
int first= startOfFirst,second=endOfFirst+1;
int result = startOfFirst;
while ((first <= endOfFirst) &&(second <= endOfSecond)){
if (x[first] <= x[second]){
y[result++] = x[first++];
}else{
y[result++] = x[second++];
}
}
while(first <= endOfFirst){
y[result++] = x[first++];
}
while (second<= endOfSecond){
y[result++] = x[second++];
}
}
7 快速排序
快速排序的基本思想的是分而治之,把n个元素分成三段,左段,中间段,右段,左段的元素都小于等于中间段元素,右段元素都大于等于中间段元素,对左段和右段独立排序,并且排序后不用合并。
函数主体由递归形式构成,代码如下,
void quickSort(int* array,int left,int right)
{
if(left<right){//递归条件
int i=left-1;
int j=right+1;
int midElement=array[(let+right)/2];
while(true){
while(array[++i]<midElement);//找左边大于等于标杆的元素
while(array[--j]>midElement);//找右边小于等于标杆的元素
if(i<j){//找到了,交换
swap(array[i],array[j]);
}else{
break; //没找到,则分成2个子序列,继续此操作
}
}
quickSort(array,left,i-1);
quickSort(array,j+1,right);
}
}
8 排序树
排序树的基本思想基于二叉搜索树的性质,在二叉搜索树中关键字有这样的性质:左节点<根<右节点,那么对二叉搜索树进行中序遍历就是二叉树节点上元素的排序结果。
基本方法是采用两个辅助数组来表示元素与元素之间在树中的关系,两个辅助数组分布是左孩子索引和右孩子索引。函数主体由两部分组成,1)构造关系树,2)中序遍历输出结果。
void treeSort(int*array, int size)
{
//用2个辅助数组来表示元素与元素之间在树中的关系
int *lChild = new int[size]; //左孩子索引
int *rChild = new int[size]; //右孩子索引
//将各节点左右子节点指针均置为-1,表示没有左右子节点
for (int i =0; i < size; ++i){
lChild[i] = -1;
rChild[i] = -1;
}
//从第2个数开始构造树
for (int i = 1; i < size; ++i)
{
int root = 0; //根节点
while (true)
{
int compare = array[i] - array[root];
if (compare > 0){//比当前值大,进入右子树
if (rChild[root] ==-1){//右子树为空,直接放置该数在此节点位置
rChild[root] = i;
break;
}else{//右子树不为空,则以右节点为新子树根节点继续放置
root = rChild[root];
}
}else{//比当前值小,进入左子树
if (lChild[root]== -1){
lChild[root] = i;
break;
}else{
root = lChild[root];
}
}
}
}
//保存排序树的中序遍历结果
int* inOrder = new int[size];
TreeInOrder(array,inOrder,0,lChild,rChild);
//将排序好的中序数组复制到原数组
copy(inOrder,inOrder+size,array);
delete inOrder;
delete rChild;
delete lChild;
}
void TreeInOrder(int* array, int* inOrder, int root, int* lChild,int* rChild)
{//中序遍历
static int i = 0;
if (root!= -1) //节点不为空
{
TreeInOrder(array,inOrder,lChild[root], lChild, rChild);//遍历左子树
inOrder[i++] = array[root]; //保存当前值
TreeInOrder(array,inOrder,rChild[root], lChild, rChild);//遍历右子树
}
}
9 基数排序
基数排序的基本思想是箱子排序,把数字按照基数(可以取10,100,1000等)划分,然后对低位至高位依次进行箱子排序,因为箱子排序是稳定排序,所以次低位相同的节点,按照最低位数字排序所得到的次序保持不变,这就保证了可以从低位至高位的排序得到最终整个数的排序结果。
需要用到链表的数据结构,代码如下
template<typename T>
void radixSort(int r,int d) //r为基数,d为按基数分解的个数
{
int m,theBin;
for (int i = 1; i <= d; ++i) //总共排序d次
{
//创建并初始化箱子,箱子的大小就是就是基数r的大小
chainNode<T>**bottom, **top;
bottom = new chainNode<T>*[r];//链表节点构成的数组,top和bottom表示不同的数组段的首尾节点
top = new chainNode<T>*[r];
for (int b = 0; b < r; ++b)
top[b] = NULL;
//将链表在的节点分配到箱子
for (; firstNode != NULL; firstNode = firstNode->next)//分配完之后链表也就变成了空表
{
m = pow(r, i);
theBin = firstNode->element%m / pow(r, i - 1);
if (top[theBin] == NULL)
top[theBin] = bottom[theBin] = firstNode;
else {
bottom[theBin]->next = firstNode;
bottom[theBin] = firstNode;
}
}
//把箱子中的节点收集到有序链表
chainNode<T>*y = NULL; //负责连接各段链表
for(int theBin=0;theBin<r;++theBin)
if (top[theBin] != NULL)
{
if (y == NULL)
firstNode = top[theBin]; //将第一个非空箱子放入链表
else
y->next = top[theBin]; //后续箱子接着放入
y = bottom[theBin];
}
if (y != NULL)
y->next = NULL; //将表的末尾下一节点置空
delete [] bottom;
delete [] top;
}
}
链表节点代码
template<typename T>
struct chainNode
{
chainNode():next(nullptr){}
chainNode(const T& element){this->element=element;}
chainNode(const T& element,chainNode* next)
{
this->element=element;
this-next=next;
}
chainNode<T>* next;
T element;
}
标注:对于一般的基数r,相应的数字分解式为x%r,(x%r^2)/r,(x%r^3)/r^2,...
各种算法的复杂度分析如下: