常见的排序算法分析

1、冒泡排序。冒泡排序执行的时间取决于比较的趟数,在最好的情况下,待排序的记录是正序,算法只执行一趟,进行了n-1次关键码的比较,不需要移动记录,时间复杂度是O(N);

最坏的情况下,是倒序,每次比较只有一个最大的记录被交换到最终的位置,故算法执行了n-1趟,每次比较的次数是n-i,关键码比较的次数是n(n-1)/2.。

平均情况的时间复杂度与最坏的情况是同一个数量级的。

空间复杂度o(1).  它是稳定的排序算法。

代码如下:

void buffersort1(int array[],int len)
{
bool exchange;//记录是否有交换
for (int i = 0; i < len-1; i++)//外层循环比较的趟数N
{
exchange = false;
for (int j = 0; j < len-i-1;j++)//内层循环每趟需要比较的次数
{
if (array[j]>array[j + 1])
{
int t = array[j];
array[j] = array[j+1];

array[j+1] = t;

                       exchange = true;

               }

}
if (exchange == false)
return;

}

2、快速排序

快速排序算法也是基于一种交换的排序算法,是对冒泡排序的一种改进。在冒泡中,记录的比较和移动是在相邻的位置进行的,记录每次交换只能后移一个位置,因而中的比较和移动的次数比较多。在快速排序中,它的思想是记录的比较和移动都是从两端向中间位置进行的,关键码较大的记录一次就能从前面移动到后面,关键小的记录一次就能从后面移动到前面。记录移动的距离较远,从而减少了总的比较次数和交换的次数。

首先选择一个轴值,将待排序的记录划分为独立两部分,左侧的记录都是小于轴值的,右侧的记录都是大于轴值的。

int partion(int arr[], int low, int high)
{
	while (low < high)
	{
		while (low < high&&arr[low] <= arr[high])
			high--;
		if (low < high)
		{
			int t = arr[low];
			arr[low] = arr[high];
			arr[high] = t;
			low++;
		}
		while (low < high&&arr[low] <= arr[high])
			low++;
		if (low < high)
		{
			int t = arr[low];
			arr[low] = arr[high];
			arr[high] = t;
			high--;
		}
	}
	return low;
}
void sort(int array[],int low,int high)
{
	if (low < high)
	{
		int pos = partion(array, low, high);
		sort(array, low, pos - 1);
		sort(array, pos + 1, high);
	}


}

快速排序的趟数取决于递归的深度,最好的情况下是每次划分对一个记录定位后,该记录的左侧和右侧序列的长度一样,在具有N个记录的序列中,对一个记录定位需要对整个序列进行扫面一遍,则所需要的时间为O(N).每次划分的时间为o(log2n),因此总的时间复杂度是nlog2n.

最坏的情况是,待排序的序列是正序或者倒序,每次划分只得到有个比上次少一个记录的子序列,另一个为空。此时必须进行N-1次递归才能吧所有记录定位,每次需要比较的次数是n-i,因此时间复杂度是o(N2).平均情况和最坏的情况是一个数量级的。

空间复杂度最好的情况是(log2n),最坏的情况是o(n-1),平均情况是o(log2n).

快速排序是一个不稳定的排序算法。

3、简单选择排序

简单选择排序算法的思想是在第I趟排序中找出最小的元素,并和第I个记录进行交换。因此选择排序算法与序列的初始状态是无关的,无论待排序的序列是什么样子的,每趟排序都是比较N-I次。总的比较次数是O(N2),这是选择排序算法最好。最坏。平均的时间复杂度。它是不稳定的排序算法。

空间复杂度是o(1).

void selectsort(int r[],int n)
{
	int j = 0;
	for (int i = 0; i < n; i++)
	{
		int index = i;
		
		for (j = i + 1; j < n; j++)
		{
			if (r[j]<r[index])index = j;
		}
		if (index != i)
		{
			int t = r[i];
			r[i] = r[index];
			r[index] = t;
		}
	}
	
}

4、堆排序算法

堆排序算法是简单选择算法的一种改进,改进的着眼点是:如何减少关键码的比较次数。简单选择排序在一趟排序中仅选出最小的关键码,没有把一趟比较的结果保存下来,因而记录比较的次数就多了。堆排序在选出最小码的同时,也找出较小的关键码,也找出较小的关键码,从而较少了比较的次数。

大根堆的定义:每个节点的值都大于等于其左右孩子的值。堆顶节点是所有节点的最大值。

堆排序思想:首先将待排序的序列建立一个大根堆,然后将堆顶元素与最后一个元素交换,再不断调整堆的过程。堆排序的运行时间主要消耗在初始建堆的过程和不断调整堆的过程。

首先是将待排序的序列建立一个堆,初始堆

void max_heapmdf(int arr[],int k,int n)//  调整堆
{
	int i = k;
	int j = 2 * k + 1;
	while (j < n)
	{
		if ((j+1 < n) && (arr[j] < arr[j + 1]))
		{
			j = j + 1;
		}
		if (arr[i]>arr[j])
			break;
		else
		{
			swap(arr[i],arr[j]);
			i = j;
			j = 2 * i + 1;
		}
		
	}
}
void buildheapmax(int arr[],int n)//建立初始堆
{
	for (int i = n / 2 - 1; i >= 0; i--)
	{
		max_heapmdf(arr, i, n);
	}

}
void heapsort1(int arr[],int n)
{   
	buildheapmax(arr, n);
	for (int i = 1; i < n; i++)
	{
		swap(arr[0],arr[n-i]);
		max_heapmdf(arr, 0, n-i);
	}
}

初始堆的建立需要O(N),第I次取堆顶元素重建堆的时间是需要O(log2i),并且需要去N-1次,因此总的时间复杂度是o(nlog2n).向下调整堆的时间与树高有关。堆排序的平均时间、最坏、最好时间都一样。对,记录的初始状态无关。

空间复杂度O(1).

5、插入算法

思想是给每个元素找位置,将每一个记录插入到已经排好序的序列中。即是将排序序列分成两组,有序组和无序组,每次从无序组中取一个元素,与有序数组中每个元素进行比较,找到合适的位置,并插入到有序数组中。一般将第一个元素视为有序数组。


void insertsort(int arr [],int len)
{
   for(int i=1;i<len;i++)
   {
      int k=i;
      int temp=arr[k];
      for(int j=i-1;(j>=0)&&arr[j]>temp;j--)
     {   arr[j+1]=arr[j];
       k=j;
     }arr[k]=temp;
    }

}

最好的情况是正序,需要比较次数是n-1次,因此时间复杂度是o(n).最坏的情况是逆序,时间复杂度是o(n2);空间复杂度是 o(1).

直接插入排序算法是一种稳定的排序算法,当序列中基本有序或者待排序记录比较少时,它是最佳的排序算法。

6、希尔排序

思想:对直接插入的改进,先将整个待排序记录分割成若干个子序列,在每个子序列中分别进行直接插入排序,待整个序列基本有序时,再对全体记录进行一次直接插入排序。

void shellsort(int array[], int len)
{
	int gap = len;
	for (gap = gap / 2; gap >0; gap = gap / 2)//注意gap 的条件,gap>0 或者gap>=1
	{
		for (int i = gap; i<len; i += gap)
		{
			int k = i;
			int temp = array[k];
			for (int j = i - gap; j >= 0 && array[j] > temp; j = j - gap)
			{
				array[j + gap] = array[j];
				k = j;
			}
			array[k] = temp;
		}
	}

}

时间复杂度是在o(n2)与o(nlog2n)之间,当n在某个特定范围内,希尔排序的时间复杂度约为o(logn1.3).空间复杂度是o(1).是不稳定的排序算法。

7.归并排序

归并排序通常采用2-路归并排序算法。采用分治法。







void Merge(int arr,int low,int mid,int high)
{int i=low;
int j=mid+1;
int k=0; 
int *temp=new int [hih-low+1];
if(!temp)
{cout<<"申请失败";
exit(0);
}
while(i<=mid&&j<=high)
{ if(arr[i]<arr[j])
  {temp[k++]=arr[i++];
  }
else
  {temp[k++]=arr[j++];
  }
}
while(i<=mid)
{
  temp[k++]=arr[i++];
}
while(j<=high)
{
  temp[k++]=arr[j++];
}
for(int i=low,k=0;i<=high;i++,k++)
{arr[i]=temp[k];
}
delete []temp;
}
void Msort(int arr[],int low,int high)
{
  if(low<high)
{
   int mid=(low+high)/2;
   Msort(arr,low,mid);
  Msort(arr,mid+1,high);
  Merge(arr,low,high,mid);
}
}

一趟归并排序需要将待排序序列扫描一遍,其时间性能是o(n),整个归并排序需要进行log2n趟,因此总的时间复杂度是o(nlog2n).这是其平均/最好、最坏的时间复杂度。

空间复杂度是o(n).是一种稳定的排序算法。

8、桶排序

将数组分配到有限数量的桶子里,每个桶子再个别排序。这时可用冒泡、快排、等。

下列中:将数据作为桶(数组)的下标存储,适合于数据值的范围比较小;比如:求在1G大小的字符串中,出现次数最多的那个字符;因为字符char的边界是[0...255],就可以将所有的字符遍历、存储到[0...255]桶中(数组下标).

  假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果  

  对每个桶中的数字采用快速排序,那么整个算法的复杂度是  

  O(n   +   m   *   n/m*log(n/m))   =   O(n   +   nlogn   -   nlogm)  

  从上式看出,当m接近n的时候,桶排序复杂度接近O(n)  


基数排序:
可以理解为对桶排序的扩充,将数据分别按个位、十位、百位...分别放入buckets[0...9][num]的桶中。
即:先将个位数存储到[0...9]的桶中,个位数将排好序
再按照个位数排好的序,将十位数存储到[0...9]的桶中,十位数排好序(个位数上一轮已经排好序)...
再百位,千位...每次放入桶中后,桶的顺序、桶内数据的顺序是排好的。
例如:
下例中数组:
int array[] = {2, 343, 342, 1, 123, 43, 4343, 433, 687, 654, 3};
按个位依次排序(到桶中)
0|
1|1,
2|2,342
3|343,123,43,4343,433,3
4|654 
5| 
6| 
7|687 
8| 
9|
按值的顺序存入到array[] = {1,2,342,343,123,43,4343,433,3,654,687};
由此可见,个位数是排好序的;

再将array[] = {1,2,342,343,123,43,4343,433,3,654,687}按十位数依次排序
0|1,2,3 
1| 
2|123  
3|433 
4|342,343,43,4343 
5|654  
6| 
7| 
8|687 
9|
按值的顺序存入到array[] = {1,2,3,123,433,342,343,43,4343,654,687}
由此可见,个位数、十位数是排好序的;

再将array[] = {1,2,3,123,433,342,343,43,4343,654,687}按百位数依次排序
0|1,2,3,43 
1|123  
2| 
3|342,343,4343   
4|433  
5|   
6|654,687 
7| 
8|  
9|
按值的顺序存入到array[] = {1,2,3,43,123,342,343,4343,433,654,687}
由此可见,个位数、十位数、百位数是排好序的; 

再将array[] = {1,2,3,43,123,342,343,4343,433,654,687}按千位数依次排序
0|1,2,3,43,123,342,343,433,654,687 
1| 
2| 
3|    
4|4343   
5|   
6|  
7| 
8|  
9|
按值的顺序存入到array[] = {1,2,3,43,123,342,343,433,654,687,4343}

这时整个数组已经按升序排序完成。

基数排序的时间复杂度,假设待排序的关键码由d 个组成,每个关键码的取值范围为m个,则基数排序的时间复杂度是o(d*(n+m))。空间复杂度是 o(m) .它是稳定的排序算法。其中d是位数, n是关键码个数,m是基数。

总结:

(1)在最好的情况下,直接插入排序和冒泡排序是最快的;在平均情况下,快速排序是最快的;在最坏的情况下,堆排序和归并排序是对快的。

(2)当N比较大,关键码分布随机,且对稳定性不做要求,采用快排;

(3)当N比较大,内存空间允许,且要求稳定,采用归并;

(4)当N比较大,关键码分布可能出现正序或者逆序,且对稳定性不做要求,采用堆排序或者归并。

(5)当B比较大,而只要找出最小前几个记录,采用堆排序或者简繁选择排序。

(6)当N比较小,记录基本有序,且要求稳定,采用直接插入。

(7)当待排记录个数比较小,记录所含数据项较多,所占的空间较大,采用简单选择。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章