总结之排序

各种排序的思路

冒泡排序:

冒泡排序主要思路是:

通过交换使相邻的两个数变成小数在前大数在后,这样每次遍历后,最大的数就“沉”到最后面了。重复N次即可以使数组有序。

冒泡排序改进1:在某次遍历中如果没有数据交换,说明整个数组已经有序。因此通过设置标志位来记录此次遍历有无数据交换就可以判断是否要继续循环。

冒泡排序改进2:记录某次遍历时最后发生数据交换的位置,这个位置之后的数据显然已经有序了。因此通过记录最后发生数据交换的位置就可以确定下次循环的范围了。


直接插入排序:

每次将一个待排序的数据,插入到前面已经排好序的序列之中,直到全部数据插入完成。

直接做法:找到要插入的位置,将插入位置后的元素后移,再将插入的元素放到插入位置。

优化一:将寻找插入元素和元素后移放到一个步骤里

优化二:元素后移用元素交换的方式取代


直接选择排序:

数组分成有序区和无序区,初始时整个数组都是无序区,然后每次从无序区选一个最小的元素直接放到有序区的最后,直到整个数组变有序区。


希尔排序:

先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。由于希尔排序是对相隔若干距离的数据进行直接插入排序,因此可以形象的称希尔排序为“跳着插”。


归并排序:

当一个数组左边有序,右边也有序,那合并这两个有序数组就完成了排序。如何让左右两边有序了?用递归!这样递归下去,合并上来就是归并排序。


快速排序:

“挖坑填数+分治法”,首先令i =L; j = R; 将a[i]挖出形成第一个坑,称a[i]为基准数。然后j--由后向前找比基准数小的数,找到后挖出此数填入前一个坑a[i]中,再i++由前向后找比基准数大的数,找到后也挖出此数填到前一个坑a[j]中。重复进行这种“挖坑填数”直到i==j。再将基准数填入a[i]中,这样i之前的数都比基准数小,i之后的数都比基准数大。因此将数组分成二部分再分别重复上述步骤就完成了排序。


堆排序:

堆排序主要思路用张图示来表示更好:


可见堆排序的难点就在于堆的的插入和删除。

堆的插入就是——每次插入都是将新数据放在数组最后,而从这个新数据的父结点到根结点必定是一个有序的数列,因此只要将这个新数据插入到这个有序数列中即可。

堆的删除就是——堆的删除就是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点开始将一个数据在有序数列中进行“下沉”。

因此,堆的插入和删除非常类似直接插入排序,只不是在二叉树上进行插入过程。所以可以将堆排序形容为“树上插


各种排序的比较

1.稳定性分析

选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法

参见:

http://baike.baidu.com/link?url=D8f5zFSgsqsJcqMmdCfWZN1M_Mz33WMeggta2335N2_RprbObe9kFaQVf8X6_3AAAvR6qtsNp8r4AbPsuJ2kWa

2.时间复杂度比较


3.辅助空间比较

线形排序、二路归并排序的辅助空间为O(n),其它排序的辅助空间为O(1);

4.其它比较

插入、冒泡排序的速度较慢,但参加排序的序列局部或整体有序时,这种排序能达到较快的速度。

反而在这种情况下,快速排序反而慢了。

当n较小时,对稳定性不作要求时宜用选择排序,对稳定性有要求时宜用插入或冒泡排序。

若待排序的记录的关键字在一个明显有限范围内时,且空间允许是用桶排序。

当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。

当n较大时,关键字元素可能出现本身是有序的,对稳定性有要求时,空间允许的情况下。

宜用归并排序。

当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序。

排序算法code实例

<span style="font-size:10px;"><span style="font-size:10px;"><pre name="code" class="cpp">#include <iostream>
using namespace std;

class Solution{
public:
	//冒泡排序
	//比较相邻的两个元素,如果前面大于后面,则交换,每一轮过后,最大的那个数就放到了最后一个
	//优化1:如果没有发生变化,那么证明已经是排好序了,则不需要再继续下一轮
	//优化2:如果有100个数的数组,仅前面10个无序,后面90个都已排好序且都大于前面10个数字。记录第一次交换的位置即可。
	void bubbleSort1(int A[],int n)
	{
		for(int i=0;i<n;i++)
			for(int j = 1;j <n-i;j++)
				if (A[j-1] > A[j])
					swap(A[j-1],A[j]);
	}

	void bubbleSort2(int A[],int n)
	{
		bool flag = true;
		int k = n;

		while(flag)
		{
			flag = false;
			for(int i = 1;i < k;i++)
			{
				if(A[i-1] > A[i])
				{
					swap(A[i-1],A[i]);
					flag = true;
				}
			}
			k--;
		}
	}

	void bubbleSort3(int A[],int n)
	{
		int pos = n;
		int k = n;
		bool flag = true;
		while(flag)
		{
			flag =false;
			k = pos;
			for(int i =1;i<k;i++)
			{
				if(A[i-1] > A[i])
				{
					swap(A[i-1],A[i]);
					pos = i;
					flag = true;
				}
			}
		}
	}

	//直接插入排序
	//将A[i],插入到[0,i-1]的有序空间,且保持有序。
	//将A[i]于A[i-1]比较,如果A[i-1]小于A[i],啥也不做;否则,交换A[i-1]和A[i],j--,一直到A[i-1]小于A[i].
	void insertSort(int A[],int n)
	{
		for(int i=1;i<n;i++)
			for(int j=i-1;j>=0&&A[j]>A[j+1];j--)				
				swap(A[j],A[j+1]);
	}

	//希尔排序
	//gap=n/2,每相隔gap个元素进行插入排序。gap=gap/2,再插入排序,直到gap=0;
	//比如10个元素,gap为5.就分成了5组数据,每组元素为2.对每组进行插入排序。
	//思路1,最紧扣定义的写法。
	void shellSort1(int A[],int n)
	{
		for(int gap=n/2;gap>0;gap=gap/2)//步长
		{
			for(int i=0;i<gap;i++)//一共gap组数据
			{
				for(int j=i+gap;j<n;j+=gap)//对每组数据进行排序
				{
					if(A[j] < A[j-gap])
					{
						int k;
						int tmp = A[j];//记录A[j]的值
						for(k = j-gap;k>=0 && A[k] > tmp;k-=gap)
						{
							//比tmp大的数,则往后移位
							A[k+gap] = A[k];
						}

						//此时A[k]是比tmp小的位置,则k+gap则是应该插入的位置
						A[k+gap] = tmp;
					}
				}
			}
		}
	}

	//上面的思路代码量比较大。思路2.从gap元素开始,每个元素与组内的元素进行插入排序。
	void shellSort2(int A[],int n)
	{
		for(int gap=n/2;gap>0;gap=gap/2)//步长
		{
			for(int i=gap;i<n;i++)
			{
				if(A[i] < A[i-gap])
				{
					int k;
					int tmp = A[i];
					for(k = i-gap;k >=0 && A[k]>tmp;k-=gap)
					{
						//比tmp大的,向后移位
						A[k+gap]=A[k];
					}
					//此时A[k]比tmp小,插入位置在k+gap
					A[k+gap] = tmp;
				}
			}
		}
	}

	//思路3.用交换的思想,可以不用考虑向后移位的问题。
	//如果A[i]>A[i-1],则不动;否则,交换,i--,再比较A[i]和A[i--],直到A[i]>A[i-1]为止。
	void shellSort3(int A[],int n)
	{
		for(int gap = n/2;gap >0;gap/=2)
			for(int i=gap;i<n;i++)
				for(int j=i-gap;j>=0 && A[j]>A[j+gap];j-=gap)
					swap(A[j],A[j+gap]);
	}

	//选择排序
	//与插入排序类似,都分为有序区和无序区。不同的是,插入排序是将第一个元素插入有序区形成更大的有序区;而选择排序是从无序区找出最小,放到无序区的最前面(有序区的最后面)。
	//最开始都是无序的,选择一个最小的与第一个交换;指向第二个,选择后面最小的与第二个交换。。。。
	void selectSort(int A[],int n)
	{
		for(int i=0;i<n;i++)
		{
			int minIdx = i;
			for(int j=i+1;j<n;j++)
			{
				if(A[j]<A[minIdx])
				       minIdx = j;
			}
			swap(A[i],A[minIdx]);
		}
	}

	//归并排序
	//递归+合并。当一个数组左边和右边有序,那么合并这两个有序数就完成了排序。如何让左右两遍都有序?用递归!递归下去,合并上来就是归并排序。
	void mergeArray(int a[],int first,int mid,int last,int tmp[])//将两个有序数组a[first,mid]和a[mid,last]合并。
	{
		int i = first,j = mid +1;
		int k=0;
		while( i<=mid && j<=last )
		{
			if(a[i]<=a[j])
				tmp[k++] = a[i++];
			else
				tmp[k++] = a[j++];
		}

		while(i <= mid)
			tmp[k++] = a[i++];

		while(j <= last)
			tmp[k++] = a[j++];

		for(i =0;i<k;i++)
			a[first+i] = tmp[i];
	}

	void mergeSort(int a[],int first,int last,int tmp[])//有些地方分配临时数组,放在mergeArray里面,但是过多的new太耗时。在一开始new一个临时数组,后面操作可以共用它。
	{
		if(first < last)
		{
			int mid = (first+last)/2;
			mergeSort(a,first,mid,tmp);//左边有序
			mergeSort(a,mid+1,last,tmp);//右边有序
			mergeArray(a,first,mid,last,tmp);//将两个有序数列合并
		}
	}

	//快速排序。挖坑填数字+分治法。
	//1.i=first,j=last.选一个基准数(一般选第一个)。将其挖出来,形成一个坑a[i];
	//2.从后向前j--找到一个比它小的数,找到后挖出来填到之前的a[i]的坑中,这时候形成一个新坑a[j];
	//3.从前向后i++找到一个比它大的数,找到之后填到此前的a[j]坑中;
	//4.重复,直到i==j,将基准数填入a[i]中。至此,基准点左边的都比它小,右边的都比它大。
	//5.以基准点为中心,分成两个序列,重复上面的步骤。
	void quickSort(int a[],int first,int last)
	{
		if(first < last)
		{
			int i=first,j=last,value=a[first];
			while(i<j)
			{
				//从右向左找到第一个小于value的数字,填到之前a[i]位置,形成一个新坑a[j]
				while(i<j&&a[j]>value)
					j--;
				if(i<j)
					a[i++] = a[j];

				//从左向右找到第一个大于value的数字,填到之前a[j]的位置上,形成一个新坑a[i]
				while(i<j&&a[i]<value)
					i++;
				if(i<j)
					a[j--]=a[i];
			}

			//将value填到a[i]中
			a[i] = value;

			//以基准点为中心分开为两个序列,重复上面步骤
			quickSort(a,first,i-1);//递归处理
			quickSort(a,i+1,last);
		}
	}

	//堆排序
	//二叉堆。父节点总是大于等于(小于等于)子节点。每一个左右子树都是一个二叉堆。父节点大于子节点的叫做最大堆,父节点小于子节点,叫做最小堆。
	//二叉堆可以用数组表示。找父节点下标,(i-1)/2;左右子节点下标,2*i+1和2*i+2
	//堆的插入。每次插入都是放到数组最后。它的父节点到根节点都是有序的,任务就是将新数据插入到有序数组中。(插入排序)
	//堆的删除。每次删除第0个数据,然后将最后一个数据的值赋给根节点,自上向下调整树。相当于从根节点将一个数据下沉的过程。
	//堆排序。1)首先堆化数组。2)A[0]与A[n-1]交换,再对A[0,n-2]重新恢复堆。第二次,将A[0]与A[n-2]交换,对[0,n-3]重新恢复堆,一直重复道A[0]与A[1]交换。
	//这样每次最小的就放到了后面的有序序列。操作完成之后,整个数组就有序了。
	
	//从第i个节点整理,从上到下。n为节点总数。
	void heapFixDown(int a[],int i,int n)
	{
		int tmp = a[i];
		int j = 2*i+1;//i的左子节点
		while(j < n)
		{
			//先找到子节点中最小的
			if(j+1 < n && a[j+1] < a[j])
				j++;

			//如果根节点比子节点都小,不用调整
			if(a[j]>=tmp)
				break;

			//将较小的子节点往上移动,替换它的父节点
			a[i] = a[j];
			i=j;
			j=2*i+1;
		}
		a[i]=tmp;
	}

	//建立最小堆
	void createMinHeap(int a[],int n)
	{
		for(int i=n/2-1;i>=0;i--)
		    heapFixDown(a,i,n);
	}

	//堆排序
	void heapSort(int a[],int n)
	{
		createMinHeap(a,n);		
		for(int i=n-1;i>=1;i--)
		{
			swap(a[i],a[0]);
			heapFixDown(a,0,i);
		}
	}
};

void main()
{
	int A[] = {7,2,3,5,9,8,10,20,15};
	int n = sizeof(A)/sizeof(int);

	printf("The original array is:\n");
	for(int i=0;i<n;i++)
	{
		printf("%d ",A[i]);
	}
	printf("\n");

	Solution *Sol = new Solution();
	//Sol->bubbleSort3(A,n);
	//Sol->selectSort(A,n);

	//int *p = new int[n];
	//if (p==NULL)
		//return;
	//Sol->mergeSort(A,0,n-1,p);
	//delete[] p;

	Sol->heapSort(A,n);
	printf("The latest array is:\n");
	for(int i=0;i<n;i++)
	{
		printf("%d ",A[i]);
	}
	printf("\n");
	delete Sol;
}</span></span>





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值