排序算法简解

目录

算法稳定性

堆排序

快速排序

C++ STL库中sort实现机制 

归并排序


算法稳定性

  排序算法稳定性的简单形式化定义为:如果Ai = Aj,排序前Ai在Aj之前,排序后Ai还在Aj之前,则称这种排序算法是稳定的。通俗地讲就是保证排序前后两个相等的数的相对顺序不变。

  对于不稳定的排序算法,只要举出一个实例,即可说明它的不稳定性;而对于稳定的排序算法,必须对算法进行分析从而得到稳定的特性。需要注意的是,排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。

  例如,对于冒泡排序,原本是稳定的排序算法,如果将记录交换的条件改成A[i] >= A[i + 1],则两个相等的记录就会交换位置,从而变成不稳定的排序算法。

  其次,说一下排序算法稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,前一个键排序的结果可以为后一个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位排序后元素的顺序在高位也相同时是不会改变的。

堆排序

堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,使得在当前无序区中选取最大(或最小)关键字的记录变得简单。堆排序是利用这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它是不稳定排序(发生在堆顶元素和最后无序元素发生置换时),不适合记录较少的排序。

调用堆的复杂度为O(log(n)),创建堆的复杂度为O(n),推导公式随后给出。

堆实际上是一棵完全二叉树。 
堆满足两个性质: 
(1) 堆的每一个父节点都大于(或小于)其子节点; 
(2) 堆的每个左子树和右子树也是一个堆。 

堆是具有以下性质:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。步骤:1.创建堆;2.调整堆;3.堆排序。

代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
/*调整堆*/
void heapAdjust(int a[], int i, int size)
{
	int leftChild = 2 * i + 1;			//根节点序号为0,size只做判断用.
	int rightChild = 2 * i + 2;
	int max = i;
	if (i <= size / 2 - 1)				//非叶节点判断,可以防止对叶节点递归
	{
		if (leftChild < size && a[leftChild] > a[max])//只记录位置,并未进行元素交换
			max = leftChild;
		if (rightChild < size && a[rightChild] > a[max])
			max = rightChild;
		if (max != i)				//如果max==i无需调整,即是局部有序,调整是从指定或者变动元素开始依次递归为子节点。
		{
			swap(a[max], a[i]);
			heapAdjust(a, max, size);//调整交换后的以较大值位置为父节点的子堆的有序性。
		}
			
	}
	
}
/*创建堆*/
void creatHeap(int a[], int size)
{
	int i;
	for (i = size / 2 - 1; i >= 0; i--)
	{
		heapAdjust(a, i, size);//此过程从最后非叶节点开始调整。

	}
}
/*堆排序*/
void heapSort(int a[], int size)
{
	int iLoop;							//实时递减的无序区堆大小,也即循环次数n
	creatHeap(a, size);				//创建大顶堆也是不断调整局部值得过程,之后进行最大元素(根节点元素)归位。
	for (iLoop = size; iLoop >= 1; iLoop--)
	{
		swap(a[0], a[iLoop -1]);			//交换大顶堆根节点和无序区最后叶节点。
		heapAdjust(a, 0, iLoop -1);		//将根节点归位后的堆重新调序为大顶堆,无序堆元素减一,此过程从根节点开始调整。(这里iLoop从size开始递减更容易理解,iLoop -1为容积减小1的堆大小)
	}
	
	
}
int main()
{
	int size;
	while (cin >> size)
	{
		int A[100], i;
		for (i = 0; i < size; i++)
			cin >> A[i];
		heapSort(A, size);
		for (i = 0; i < size; i++)
			cout << A[i] << ' ';
		cout << endl;
	}
	system("pause");
}

快速排序

归并排序和快速排序都是一种分治思想的递归排序。

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。

快速排序的精髓在于参考值的选取,可以采用三数中值分割法来选取合适的参考值。具体步骤为,选取数组元素头尾和中间数[left+right]/2三个数据的中位数作为参考值。注意此时三个元素已经有序,可以节约时间。

代码如下(暂未进行参考元素优化):

#include<iostream>
#include<algorithm>
using namespace std;
void quickSort(int *a, int left, int right)
{
	/*基准值选择为最左边元素a[left]*/
	int i, j;
	if (left>right)
		return;							//不满足条件退出
	i = left;
	j = right;
	while (i != j)
	{
		while (i < j&&a[j] >= a[left])
			j--;						//寻找每次循环第一个小于基准值的元素位置j
		while (i < j&&a[i] <= a[left])
			i++;						//寻找每次循环第一个大于基准值的元素位置j
		if (i < j)						//满足条件,交换两个元素的值
			swap(a[i], a[j]);			//交换每次循环找到的第一个大于和小于基准元素的两个元素的值
	}
	swap(a[i], a[left]);				//交换基准元素和每一轮迭代满足条件的最后一轮循环的小于基准元素的值,即将基准元素归位。(注意若基准元素为最右侧值,则改为最后一轮循环大于基准元素的值)
	quickSort(a, left, i - 1);			//分治思想的体现
	quickSort(a,  i + 1, right);
}
int main()
{
	int size;
	while (cin >> size)
	{
		int A[100], i;
		for (i = 0; i < size; i++)
			cin >> A[i];
		quickSort(A, 0, size - 1);
		for (i = 0; i < size; i++)
			cout << A[i] << ' ';
		cout << endl;
	}
	system("pause");
}

C++ STL库中sort实现机制 

实现方式具体参考原创博客:https://www.cnblogs.com/fengcc/p/5256337.html ,感谢博主!!

归并排序

归并排序是创建在归并操作上的一种有效的排序算法,效率为O(nlogn),1945年由冯·诺伊曼首次提出。

归并排序算法主要依赖归并(Merge)操作。归并操作是指合并两个已经排序好的表,因为两个表示排序好的,所以若将输出放在第三个表中时,该算法可以通过对输入数据一趟排序完成。

步骤:

1.创建动态数组,用于存放所有归并操作后的合并数组。

2.分治递归,将数组递归分解为单一元素。

3.递归返回,进行归并操作。注意所有归并后元素都存放在唯一动态分配的内存中。

代码如下(内存优化):


/*此历程进行内存优化,参考数据结构和算法分析P175,常规Merge操作会每一次内部声名一个临时数组,
这样任一时刻最多可存在LOG(N)个临时数组。(Merge发生在所有递归条件不满足即只剩下一个元素,则开始返回并进行归并。)
占用内存过多。 若进行动态内存获取和释放则很浪费时间,丢失了时间效率。
严密测试支出,由于Merge操作位于递归函数的最后一步,因此任一时刻只需要一个临时数组活动,而且可以使用该临时数组的任一部分,则最为简便。
*/
#include<iostream>
using namespace std;
void mergeOperation(int a[],int pTemp[],int firstPartBegain,int secondPartBegain,int secondPartEnd)
{
	int i, firstPartEnd, size, tempPos;
	firstPartEnd = secondPartBegain - 1;
	tempPos = firstPartBegain;
	size = secondPartEnd - firstPartBegain+1; //该次递归返回的数组大下。
	while (firstPartBegain <= firstPartEnd && secondPartBegain <= secondPartEnd)
		if (a[firstPartBegain] <= a[secondPartBegain])
			pTemp[tempPos++] = a[firstPartBegain++];
		else
			pTemp[tempPos++] = a[secondPartBegain++];
	while(firstPartBegain <= firstPartEnd)
		pTemp[tempPos++] = a[firstPartBegain++];//若第一部分剩余,将第一部分剩下的补到动态临时数组中。
	while (secondPartBegain <= secondPartEnd)
		pTemp[tempPos++] = a[secondPartBegain++];//同理
	for (i = 0; i < size; i++,secondPartEnd--)
		a[secondPartEnd] = pTemp[secondPartEnd];//注意只有secondPartEnd是未改变的定值,firstPartBegain已经自增到firstPartEnd。

}
void mergeSortRecursion(int a[],int pTemp[],int left,int right)
{
	int middle;
	if (left < right)
	{
		middle = (left + right) / 2;				//第一部分尾部位置
		mergeSortRecursion(a, pTemp ,left, middle );//第一部分分治
		mergeSortRecursion(a, pTemp ,middle+1, right);//第二部分分治
		mergeOperation(a, pTemp, left, middle+1, right);
	}
}
void mergeSort(int a[], int size)
{

	int *pTemp = new int[size * sizeof(int)];
	if (pTemp != NULL)
	{
		mergeSortRecursion(a, pTemp, 0, size - 1);
		delete [] pTemp;
	}
	else
		cout <<"no space for users to achieve,fatal error appeared"<<endl;

}

int main()
{
	int size;
	while (cin >> size)
	{
		int A[100];
		for (int i = 0; i < size; i++)
			cin >> A[i];
		mergeSort(A, size);
		for (int i = 0; i < size; i++)
			cout << A[i]<<' ';
		cout << endl;
	}
	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/zhang_postgradu/article/details/82938908
今日推荐