排序算法总结(C++描述)

排序算法可以分为两类,低级排序算法和高级排序算法

低级排序算法:选择排序,冒泡排序,插入排序,希尔排序

高级排序算法:堆排序,归并排序,快速排序,排序树,基数排序

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,...

各种算法的复杂度分析如下:

猜你喜欢

转载自blog.csdn.net/Jeff_Winger/article/details/80791306
今日推荐