七种常见经典排序算法

功能:七种常见经典排序算法
作者:wikiwen
日期:2018年4月20日
参考文章:
http://yansu.org/2015/09/07/sort-algorithms.html
https://zh.wikipedia.org/wiki/排序算法
https://en.wikipedia.org/wiki/Quicksort

分类:

  1. 插入排序:直接插入排序、希尔排序
  2. 交换排序:冒泡排序、快速排序
  3. 选择排序:简单选择排序、堆排序
  4. 归并排序
    快速排序和归并排序均用递归法实现。

演示
七种常见经典排序算法_第1张图片http://sorting.at/

1 直接插入排序、希尔排序

#include 
#include 
using namespace std;

/***********************************【插入排序】*************************************************************/
/*
* 排序方法:直接插入排序(插入排序)
* 思路:不断构建有序序列,对于剩余未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
*		在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
* 时间复杂度O(n)~O(n^2)(分别表示最好情况、最好情况,下同),平均时间复杂度O(n^2)
  空间复杂度О(n) total, O(1) auxiliary
*/
void insert_sort(vector<int>& a)
{
	int n = a.size();
	for (int i = 1; i <= n - 1; i++)//i = 1~n-1, j = i-1~0
	{
		int temp = a[i];
		for (int j = i - 1; j >= 0; j--)//从已排序序列中从后往前比较
		{

			if (temp < a[j])//用原始a[i](temp)依次与之前数据比较,插入合适位置
			{
				a[j + 1] = a[j];//往后移
				a[j] = temp;
			}
			else
				break;//如果a[i]位置适当,直接跳出循环
		}
	}
}
/*
* 排序方法:希尔排序(插入排序)
* 思路:希尔排序(也称缩小增量排序算法)是基于插入排序的以下两点性质而提出改进方法的:
	插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
	插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位(提升比较的跨度)
* 具体做法:希尔排序将数组按照一定步长分成几个子数组进行排序(直接插入),再对已经有序的子数组,通过逐渐减短步长来完成最终排序
* 时间复杂度O(nlogn)~O(n^2)(分别表示最好情况、最好情况,下同),平均时间复杂度取决于步长序列
  空间复杂度О(n) total, O(1) auxiliary
*/
void shell_sort(vector<int>& a)
{
	int n = a.size();
	for (int gap = n / 2; gap > 0; gap /=2)//以二分步长为例
	{
		for (int i = gap; i < n; i++)//i = gap~n-1, j = i-gap~0(间隔gap)
		{
			int temp = a[i];
			for (int j = i - gap; j >= 0; j -= gap)//直接插入排序
			{
				if (temp < a[j]) // 用原始a[i](temp)依次与之前数据比较,插入合适位置
				{
					a[j + gap] = a[j];//往后移
					a[j] = temp;
				}
				else
					break;
			}
			/*for (int j = i - gap; j >= 0 && temp < a[j]; j -= gap)
			{
				a[j + gap] = a[j];//往后移
			}
			a[j + gap] = temp;//插入*/
		}
	}
}

2 冒泡排序、快速排序


/***********************************【交换排序】*************************************************************/
/*
* 排序方法:冒泡排序(交换排序)
* 默认排序方式:增序
* 思路:每趟冒泡找出一个最大值到末尾
* 时间复杂度 O(n^2),
  空间复杂度是O(1)(指辅助空间)
*/
void bubble_sort(vector<int>& a)
{
	for (int i = 0; i < a.size() - 1; i++)//作n-1趟排序,每一趟找最大值到末尾 
	{
		for (int j = 0; j < a.size() - 1 - i; j++)//每一趟,从a[0]找到a[n-2-i],分别与a[1]到a[n-1-i]比较
		{
			if (a[j] > a[j + 1]) swap(a[j], a[j + 1]);//交换元素(记录)
		}
	}
}
/*
* 排序方法:快速排序(交换排序)
* 思路:快速排序是利用分治法实现的一个排序算法,快速排序和归并排序不同,
	   它不是一半一半的分子数组,而是选择一个基准数,把比这个数小的挪到左边,
	   把比这个数大的移到右边。然后不断对左右两部分也执行相同步骤,直到整个数组有序。
* 说明:对冒泡排序的一种改进,在所有同数量级的排序方法中,快速排序的常数因子k最小,
       就平均时间而言,快速排序是目前被认为是最好的一种内部排序方法。
* 时间复杂度O(nlogn)~ O(n^2),平均时间复杂度O(nlogn)(平均logn次partition,每次partitions时间复杂度O(n))
  空间复杂度O(logn)(指额外空间复杂度,递归造成的栈空间的使用)
*/
int partition(vector<int>& a, int left, int right);
void quick_sort(vector<int>& a, int left, int right)
{
	
	if (left >= right) return;				//递归出口
	int pivotLoc = partition(a, left, right);	//将序列一分为二
	quick_sort(a, left, pivotLoc - 1);		//对左子表递归排序
	quick_sort(a, pivotLoc + 1, right);		//对右子表递归排序
}

//分割函数
//选择一枢轴分割序列,并返回其位置
int partition(vector<int>& a, int left, int right)
{
	//1. 初始化,用序列的第一个元素作为枢轴(也可用其他元素,但是要把枢轴元素暂时放到起始位置,方便后续交换,如三数中值初始化枢轴)
	//2. median3
	//3. srand((unsigned)time(NULL));  用随机法较简单
		// int pivotPos = rand() % (right - left) + left;   //得到随机基元的位置(下标) 
		// swap(a[pivotPos], a[left]) //将枢轴暂时放入起始位置
	int pivot = left;			
	while (left < right)	//从序列的两端交替地向中间扫描(在此循环中a[pivot]不动,退出循环后在被交换)
	{
		//先right再left以使left最后指向等于枢轴位置元素或者小于枢轴位置的元素(这样与a[left]交换才不会出错)
		while (left < right&&a[right] >= a[pivot]) right--;//找到本次扫描中第一个不满足枢轴规律的高位数
		while (left < right&&a[left] <= a[pivot]) left++;	//找到本次扫描中第一个不满足枢轴规律的低位数
		swap(a[left], a[right]);						//交换以使满足枢轴规律
	}//最后结果是left和right均指向枢轴位置
	swap(a[left], a[pivot]);//将枢轴移动到位
	return left;			//返回枢轴位置
}
//三数中值初始化枢轴(选择序列两端和中间数的中值作为枢轴,减少了与预排序输入时带来的问题)
int median3(vector<int>& a, int left, int right)
{
	int center = (left + right) / 2;
	if (a[center] < a[left])
		swap(a[left], a[center]);
	if (a[right] < a[left])
		swap(a[left], a[right]);
	if (a[right] < a[center])
		swap(a[center], a[right]);

	// Place pivot at position 第一个位置
	swap(a[center], a[left]); //将枢轴暂时放入起始位置
	return a[left];

}

3 简单选择排序、堆排序

/***********************************【选择排序】*************************************************************/
/*
* 排序方法:简单选择排序(选择排序)
* 思路:每趟遍历选择最小值,放于序列前面(递增排序时)
* 时间复杂度O(n^2),空间复杂度О(n) total, O(1) auxiliary
*/
void select_sort(vector<int>& a)
{
	int len = a.size();
	for (int i = 0; i < len - 1; i++)
	{
		for (int j = i+1; j < len; j++)//每一趟遍历选择最小值,放于序列前面
		{
			if (a[j] < a[i]) swap(a[j], a[i]);
		}
	}
}

/*
* 排序方法:堆排序(选择排序)
* 思路:最大堆(大顶堆)的第一位总为当前堆中最大值,所以每次将最大值输出后,
	   调整堆即可获得下一个最大值,通过一遍一遍执行这个过程就可以得到前k大元素,或者使堆有序
	   (每次选择最大值,放于序列后面,调整a[0~i-1],i为末尾序号,i=len-1~0)
* 时间复杂度O(nlogn),空间复杂度O(n) total, O(1) auxiliary

* 其他说明:
	父结点均大于或小于左右子结点的树为堆,其为一个完全二叉树
	i节点的父节点 parent(i) = floor((i-1)/2)
	i节点的左子节点 left(i) = 2i + 1 (i从0开始,堆顶元素索引为0)
	i节点的右子节点 right(i) = 2i + 2
	(堆从上到下,从左到右,序号依次增加,堆顶元素序号为0)
* 步骤:
	1.构造最大堆(Build_Max_Heap):从无序序列开始建堆,从最后一个非终端结点(父结点)开始调用堆调整算法,一直到第一个非终端结点调整好
	2.最大堆调整(Max_Heapify):除了当前父结点,序号之后的部分都满足堆的定义,调整当前结点,使满足堆的定义
	3.堆排序(HeapSort):输出堆顶元素,以堆中最后一个元素代替,重复执行2,直到所有堆顶元素输出
*/
void max_heapify(vector<int>& a, int start, int end);
void heap_sort(vector<int>& a)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
{
	
	int len = a.size();
	//构造大顶堆(n/2*logn):从最后一个父结点开始进行堆调整(从a[len/2-1]调整到a[0])
	for (int i = len / 2 - 1; i >= 0; i--)//最后一个结点序号为len-1,带入公式得其父结点序号为len/2-1
	{
		max_heapify(a, i, len - 1); //end固定,start变
	}

	//堆排序(n*logn):输出堆顶元素,以堆中最后一个元素代替,重复进行堆调整,直到所有堆顶元素输出(从a[len-1]调整到a[1])
	for (int i = len - 1; i > 0; i--)
	{
		swap(a[0], a[i]); //将堆顶元素(当前最大值)换至末尾,再对剩余的元素进行堆调整,不断重复这个过程得到增序排列的数组
		max_heapify(a, 0, i - 1); //调整a[0~i-1]元素,以供输出其中的最大值,start固定,end动
	}
}
//除了a[start],其余部分都符合堆定义,函数功能为将a[start~end]调整为堆
//一次堆调整操作平均时间复杂度为O(logn)
void max_heapify(vector<int>& a,int start,int end)
{
	int dad = start;
	int son = 2 * dad + 1;
	while (son <= end)
	{
		if (son + 1 <= end&&a[son + 1] > a[son]) son++;//选择较大的子结点,供之后与父结点比较
		if (a[dad] < a[son])//如果不符合堆规律,则交换(顺着要交换的链路走就可以了,其他不用交换的部分之前已经调整好了)
		{
			swap(a[dad], a[son]);
			dad = son;
			son = 2 * dad + 1; //继续判断下一个结点
		}
		else//如果一旦某个地方符合,则退出函数,由于已经假定其他部分符合堆的定义,故可以直接退出
		{
			return;
		}
	}
}

4 归并排序

/***********************************【归并排序】*************************************************************/
/*
* 排序方法:归并排序
* 思路:归并操作(merge),指的是将两个已经排序的序列合并成一个有序序列的操作。
	归并排序是采用分治法(Divide and Conquer)的一个典型例子。这个排序的特点是
	把一个数组分成许多子数组,对子数组排序,再将排序好的子数组归并为一个有序数组。
	可以把归并排序理解为将原数组变成一个平衡二叉树,每次分别对左右子树的结点排序,并进行归并,归并的次数就是树的高度logn
* 时间复杂度O(n)~O(nlogn),平均时间复杂度O(nlogn)
  空间复杂度О(n) total, O(n) auxiliary
*/
void merge(vector<int>& a, int begin, int middle, int end);
void merge_sort_recursive(vector<int>&a, int begin, int end);
void merge_sort(vector<int>&a)
{
	//vector temp = a;
	merge_sort_recursive(a, 0, a.size() - 1);//传递初始索引
}
void merge_sort_recursive(vector<int>&a, int begin, int end)//begin,end均为取得到的索引
{
	if (begin >= end) return;//递归的出口
	int middle = (begin + end) / 2;
	merge_sort_recursive(a, begin, middle);//对左子数组归并排序
	merge_sort_recursive(a, middle+1, end);//对右子数组归并排序
	merge(a, begin, middle, end);//归并左子数组和右子数组
}
//合并数组a左右子数组(已排序)到临时数组temp,再复制到数组a中
void merge(vector<int>& a, int begin, int middle, int end)
{
	vector<int> temp(end - begin + 1); //开辟临时数组
	int i = begin, j = middle+1, k = 0;//分别表示数组左边第一个、右边第一个、临时数组第一个元素索引
	while (i <= middle && j <= end)//索引范围:i = begin~middle,j = middle+1~end,k = begin~end,a[i],a[j],temp[k]
	{
		temp[k++] = (a[i] < a[j]) ? a[i++] : a[j++];//合并,注意k++,i++,j++
	}
	while (i <= middle) temp[k++] = a[i++];//将剩余元素复制到temp数组中
	while(j <= end) temp[k++] = a[j++];

	for (int i = begin,k = 0; i <= end, k<temp.size(); i++,k++)//将排好序的临时数组复制到数组a中(vector可以直接用赋值语句整体赋值)
	{
		a[i] = temp[k];
	}
}

你可能感兴趣的:(编程)