排序算法总结

索引

  • 1 时间复杂度
  • 2 排序
    • 2.1 稳定性
    • 2.2 内排序与外排序
    • 2.3 七种常见比较排序算法
      • 2.3.1 冒泡排序【交换】
      • 2.3.2 简单选择排序
      • 2.3.3 直接插入排序
      • 2.3.4 希尔排序【插入】
      • 2.3.5 堆排序【选择】
        • 2.3.5.1 二叉树
      • 2.3.6 归并排序【归并】
      • 2.3.7 快速排序【交换】
    • 2.4 三种常见非比较排序算法
      • 2.4.2 桶排序
      • 2.4.2 计数排序【桶】
      • 2.4.3 基数排序

补充:动态规划与分治法的本质区别
① 分治法将分解后的子问题看成相互独立的。
② 动态规划将分解后的子问题理解为相互间有联系,有重叠部分。

算法 最好时间复杂度 最坏时间复杂度 空间复杂度 排序方式 稳定性 算法设计策略
冒泡【交】 n【flag判是否交换】 n2 1 稳定
选择 n2 n2 1 不稳定
插入 n【基本有序 n2 1 稳定
希尔【插入】 与增量相关还在研究 n2 1 不稳定
归并 nlogn nlogn n 稳定 分治
快速【交换】 nlogn n2有序 logn~n 不稳定 分治
堆【选择】 稳定

通常说空间复杂度一般指的是额外空间复杂度。
k: “桶”的个数

1 时间复杂度

排序算法总结_第1张图片 主定理: 排序算法总结_第2张图片

2 排序

2.1 稳定性

关键字相同的序列在排序前后,序列不变

2.2 内排序与外排序

排序时记录是否全部放在内存中
外排序需要多次内外存之间数据交换,接下来,主要看内排序。

2.3 七种常见比较排序算法

比较排序适用于一切需要排序的情况。
比较排序最优的情况下,时间复杂度=O(n logn)
涉及算法注意观察交换三步骤 标记

2.3.1 冒泡排序【交换】

n比较相邻两数交换找到最值,然后 n-1 次比较找到次值……较小的数字像气泡一样浮出。

平均时间复杂度=O(n2)

优化思路:如果在一次找较小(大)值的循环中都没有交换次序,那么意味着排序成功,接下来的循环也就可以省去。引入标记变量跳出循环。此时若有序,时间复杂度=O(n)

void bubbleSort(int *arr,int n)
{
     
	int swap,i,j;
	for(i=0;i<n-1&&flag;i++)
	{
     
	    flag=0;//没有交换则跳出循环
		for(j=0;j<n-1-i;j++)
		{
     
			if(arr[j]>arr[j+1])
			{
     
				swap=arr[j];//!
				arr[j]=arr[j+1];//!
				arr[j+1]=swap;//!
				flag=1;//!
			}
			}}
}

2.3.2 简单选择排序

n比较相邻两数交换一次将最值放到位置上,然后 n-1 次比较找到次值……相比冒泡少了很多交换,多了一个暂存最值的空间。

平均时间复杂度=O(n2)

void selectSort(int *arr,int n)
{
     
	int swap,i,j;
	int min;
	for(i=0;i<n-1;i++)
	{
     
	    min=i;
		for(j=i+1;j<n-1;j++)
		{
     
			if(arr[min]>arr[j])
			{
     
				min=j;
			}
		}
		if(i!=min)
		{
     
		    swap=arr[min];//!
			arr[min]=arr[i];//!
			arr[i]=swap;//!
		}
	}
}

2.3.3 直接插入排序

直接插入原有的序列中
设立哨兵,临时存储和判断数组边界

平均时间复杂度=O(n2)

void insertSort(int *arr,int n)
{
     
	int i,j;
	for(i=2;i<n-1;i++)//插入,从第二个【0为哨兵】开始
	{
     
	   if(arr[i-1]>arr[i])//i需要插入
      {
     
     	 arr[0]=arr[i];//!设置哨兵,将待插入数暂时存储在0
		 for(j=i-1;arr[j]>arr[0];j--)
		{
     
		 arr[j+1]=arr[j];//!后移
		 }
		 arr[j+1]=arr[0];//!找到位置插入
	 }}
}

2.3.4 希尔排序【插入】

又称递减增量排序算法
先用直接插入法比较并调序交换 i 与 i+increment,然后缩小增量至1,由基本有序逐步达到有序。

涉及时间复杂度,增量的选取很关键,素数比较好
**时间复杂度=O(n1.3~2)**还在研究

平均时间复杂度=O(n logn)

void shellSort(int *arr,int n)
{
     
	int i,j;
	int increment=n;
	do
	{
     
		increment=increment/3+1;//随意选取的增量
		for(i=increment+1;i<n-1;i++)//插入,从第二个分组【0为哨兵】开始
		{
     
	   		if(arr[i]<arr[i-increment])//i需要插入
      	   {
     
     	 		arr[0]=arr[i];//!设置哨兵,将待插入数暂时存储在0
		 		for(j=i-increment;j>0&&arr[j]>arr[0];j-=increment)
				{
     
		 			arr[j+increment]=arr[j];//!后移,寻找插入位
		 		}
		   		 arr[j+increment]=arr[0];//!找到位置插入
	       }//if
	    }//for
 }while(increment>1);
}

2.3.5 堆排序【选择】

优先队列通常采用数据结构实现,向一个优先队列中插入元素的时间复杂度=O(logn)

在n个记录中选择一个最小的记录需要比较 n-1次,简单选择排序没有将之前的比较结果保存下来,所以次数较多。

2.3.5.1 二叉树

特殊二叉树:
(1)满二叉树: 除叶子结点外,所有结点都有两个结点,叶子结点的left,right为NULL.
(2)哈夫曼树:又称最优二叉树,带权路径最短的树。哈夫曼编码应用哈夫曼树来进行编码压缩。
(3)完全二叉树: 除最底层的叶子结点外,其余层全满,而且最底层的叶子结点集中在左端。是一种特殊的完全二叉树
(4)平衡二叉树:左右两个子树的高度差的绝对值不超过 1。包括AVL树,红黑树.

堆是具有下列性质的完全二叉树:
某个节点的值总是不大于(大顶堆)或不小于(小顶堆)其父节点的值

在下面排序算法中,完全二叉树采用顺序存储于一维数组。
排序算法总结_第3张图片
可推出 n/2+1~n 为叶子结点
(1)先构造一个大顶堆(堆顶一定是最大)
(2)然后将堆顶与堆最后一个元素交换,堆元素减一
(3)调整堆为大顶堆
(…)重复(2)(3)
时间复杂度=O(n logn)

void heapSort(int *arr,int n)
{
     
	int i;
	for(i=n/2;i>0;i--)//
	{
     
	   heapAdjust(arr,i,n);//构造大顶堆,从底开始将最大的推到顶上
	}
	  for(i=n;i>1;i--)//将大顶堆的顶挪到最后,一次得出最大值
	{
     
	  swap(arr,1,i);//最大值
	   heapAdjust(arr,1,i-1);//堆减一,调整堆
	}
}
/*i结点往前已经满足堆的定义*/
void heapAdjust(int *arr,int i,int n)
{
     
	int j,temp;
	temp=arr[i];//!
	for(j=2*i;j<n-1;j*=2)/从左孩子开始向下筛选
	{
     
		if(j<n&&arr[j]<arr[j+1]) ++j;//j为较大的
		if(temp>=arr[j]) break;//父节点>孩子,不交换
		arr[i]=arr[j];//!
		i=j;//!
	}
	arr[i]=temp;//!
}

2.3.6 归并排序【归并】

采用分治法(Divide and Conquer)的典型的应用。
将已有序的子序列合并,得到序列。
若将两个有序表合并成一个有序表,称为二路归并。

时间复杂度=O(n logn)
算法采用递归调用,所以构建了一个函数。

#define ArrLen N
void merge(int arr[], int start, int mid, int end) {
     
	int result[ArrLen];
	int k = 0;
	int i = start;
	int j = mid + 1;
	while (i <= mid && j <= end) {
     
		if (arr[i] < arr[j]){
     
			result[k++] = arr[i++];
        }
		else{
     
			result[k++] = arr[j++];
        }
	}
	if (i == mid + 1) {
     
		while(j <= end)
			result[k++] = arr[j++];
	}
	if (j == end + 1) {
     
		while (i <= mid)
			result[k++] = arr[i++];
	}
	for (j = 0, i = start ; j < k; i++, j++) {
     
		arr[i] = result[j];
	}
}
 
void mergeSort(int arr[], int start, int end) {
     
	if (start >= end)
		return;
	int mid = ( start + end ) / 2;
	mergeSort(arr, start, mid);
	mergeSort(arr, mid + 1, end);
	merge(arr, start, mid, end);
}

补:将由递归实现的归并排序算法,转化成迭代可以减少时间和空间性能上的损耗。

递归(recursion):描述以自相似方法重复事物的过程,在数学和计算机科学中,指的是在函数定义中使用函数自身的方法。(A调用A)
迭代(iteration):重复反馈过程的活动,每一次迭代的结果会作为下一次迭代的初始值。(A重复调用B)

2.3.7 快速排序【交换】

分治算法设计策略
选定一个元素作为中心轴,通过一趟排序将要排序的数据分割成独立的两部分。

平均时间复杂度=O(n logn)

2.4 三种常见非比较排序算法

计数排序、基数排序、桶排序则属于非比较排序 ,均稳定。
算法时间复杂度为O(n):非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。
但由于非比较排序需要占用空间来确定唯一位置,所以对数据规模和数据分布有一定的要求。

2.4.2 桶排序

桶排序的核心思想是把要排序的数据分组放到桶里,然后再把每个桶里的数据单独进行排序,之后把数据取出,组成的序列就是有序的了。主要应用于处理数值分布比较均匀但是数据量很大的场景。

2.4.2 计数排序【桶】

特殊的桶排序,每个桶只有一个数。

排列数值范围在0-n间的数列
(1)设置一个长度为n+1的数组,用下标表示数值,数组元素表示出现个数。
e.g:3个5 ==> arr[5]=3
(2)将个数转化为下标数值对应的位置,arr[3]=arr[2]+arr[1]
(3)依照原数组和范围数组,来排序

2.4.3 基数排序

依次依据个位、十位、百位……最高位进行排序
排序算法总结_第4张图片

你可能感兴趣的:(算法设计,软件设计师复习,排序算法)