八大排序(1)--------插入排序(直接插入、希尔)、选择排序(选择排序、堆排序)

排序算法

  • 一、排序介绍
      • 1、排序算法分类
      • 2、排序性能指标
  • 二、插入排序
      • 1、直接插入排序
      • 2、希尔排序
  • 三、选择排序
      • 1、选择排序
      • 2、堆排序

一、排序介绍

排序算法,顾名思义,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

1、排序算法分类

对于排序算法,主要分为两大类:内部排序和外部排序

  • 内部排序是数据记录在内存中进行排序;
  • 外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
    • 比如:100亿个整型数据需要被排序,这写数据需要占(100亿*4字节)/1000^3=大概需要40G内存空间。而我们的电脑也就是4G或者8G,根本存不下,此时就不适合内部排序。

我们今天说的八大排序都是内部排序,内部排序又分为几大类,如下图所示:
八大排序(1)--------插入排序(直接插入、希尔)、选择排序(选择排序、堆排序)_第1张图片

2、排序性能指标

对于一个排序算法的性能判断,主要有以下几个指标:

  1. 时间复杂度
  2. 空间复杂度
  3. 稳定性: 两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算法。 如下图所示:八大排序(1)--------插入排序(直接插入、希尔)、选择排序(选择排序、堆排序)_第2张图片

二、插入排序

插入排序主要有两个排序算法,分别是:直接插入排序和希尔排序

1、直接插入排序

1)基本思想: 将待插入的数据依次和前面已经排好序的序列中,从而有序的元素+1,依次向后遍历待插入元素。

2)步骤:

  1. i从数组下标为1的元素开始遍历,将array[i] 放到临时变量tmp中
  2. j从i-1的位置依次向前根跟tmp中的值比较:
    • 如果tmp的值小于array[j],将j下标对应的元素赋给j+1(即:array[j+1]=array[j]);j向前继续遍历(j - -);
    • 否则,将tmp直接插入到j+1的位置

代码:

public void insertSort(int [] array)
{
	for(int i=1;i<array.length;i++)
	{
		int tmp=array[i];
		int j=i-1for(;j>=0;j--)
		{
			if(array[j]>tmp)
			{
				array[j+1]=array[j];
			}else{
				break;
			}
		}
		//插入元素
		array[j+1]=tmp;
	}
}

3)时间复杂度:

  • 最差场景下:O(N^2)------>元素是逆序
  • 最优情况下:O(N)------->前提是有序的

根据时间复杂度可得出:元素越有序,排序速度越快

4)空间复杂度:O(1)---->排序中没有用到辅助空间

5)稳定性: 是稳定的排序

  • 因为条件中只有array[j] > tmp是才交换,并不会涉及到两个相等的元素交换位置,所以是稳定的;
  • 如果把条件换成array[j] >= tmp,那么此时的直接插入排序就是一个不稳定的排序。

总结:

  • 如果他是稳定的排序,在比较的过程中,就没有跳跃式的交换
  • 如果一个排序是稳定的排序,那麽他就可以变为一个不稳定的排序;但是一个排序本身是不稳定的排序,你就不可能把他变为稳定的排序

6)应用场景:

直接插入排序是比较耗费时间的操作,因为要搬移元素。所以如果序列是接近有序或者数据个数比较少比较适合插入排序

2、希尔排序

简单来说,希尔排序就是将一组元素分组后进行直接插入排序

1)思想: 先通过分组,将序列变成接近有序,数据变少

所谓的接近有序就是:小的元素尽量靠前,大的元素尽量靠后,不大不小的元素靠中间。

那么希尔是如何分组的呢?
他每次将这组数分为m组,m一般为素数,分别对各组内进行直接插入排序,直到最后将整体看为1组进行直接插入排序。举个例子:
{ 9,1,2,5,7,4,8,6,3,5}
八大排序(1)--------插入排序(直接插入、希尔)、选择排序(选择排序、堆排序)_第3张图片
代码:

public  void shellSort(int [] array)
{
	int gap=array.length;
	while(gap>1)
	{
		gap/=3;
		for(int i=gap;i<array.length;i++)
		{
			int tmp=array[i];
			for(int j=i-gap;j>=0;j-=gap)
			{
				if(array[j]>tmp)
				{
					array[j+gap]=array[j];
				}else{
					break;
				}
			}
			array[j+gap]=tmp;
		}
	
	}
}

3)时间复杂度:O(N ^ 1.25)~O(1.6N^1.25) ---------->gap的取值不一样导致时间复杂度不一样
八大排序(1)--------插入排序(直接插入、希尔)、选择排序(选择排序、堆排序)_第4张图片

  • 最差场景下:O(N^2)------>元素是逆序
  • 最优情况下:O(N)------->前提是有序的
  • 平均情况下:O(N^1.3)

4)空间复杂度:O(1)---->排序中没有用到辅助空间

5)稳定性:不稳定的排序; 该排序算法在排序过程中是否将跨元素进行交换

6)使用场景:元素比较凌乱,个数比较多

三、选择排序

选择排序主要有两个排序算法,分别是:选择排序和堆排序

1、选择排序

1)思想: 每一次从无序区间选出最大(或最小)的一个元素,存放在无序区间的最后(或最前),直到全部待排序的数据元素排完。

2)步骤:

  • 每趟开始之前 i 从0开始且 j=i+1
  • i 固定不动,i 下标的元素和 j 下标的元素比较,
    • 如果array[i]>array[j],两者交换
    • 否则 j 依次向后移动并和 i 下标的元素比较。

3)代码:

public void selectSort(int [] array)
{
	for(int i=0;i<array.length;i++)
	{
		for(int j=i+1;j<array.length;j++)
		{
			if(array[i]>array[j])
			{
				int tmp=array[i];
				array[i]=array[j];
				array[j]=tmp;
			}
		}
	}
}

4)时间复杂度: O(N^ 2) ----->论是有序还是无序都是O(N^2)

5) 空间复杂度:O(1)

6)稳定性:不稳定排序,发生跳跃式的交换

2、堆排序

在上一篇博客中主要说了堆的概念和堆的基本操作,对于堆的两大应用,其中一个就是现在要说的堆排序。

什么是堆排序呢?就是借助大顶堆或者小顶堆来对一组数据进行排序。
1)基本原理: 也是选择排序,只是不在使用遍历的方式查找无序区间的最大的数,而是通过堆来选择无序区间的最大的数。

注意:排升序要建大堆;排降序要建小堆。

2)思路:

我们以升序为例,

  • 建堆:首先对这个数组中的元素建一个大堆,
  • 从最后一个元素依次向前遍历,直到堆顶。即循环执行如下操作:
    • 交换:最后一个元素和堆顶元素进行交换(最大的元素就在最后)
    • 向下调整:交换完为了保证是大顶堆,需要将堆顶元素向下调整

3)代码:

public void heapSort(int [] array)
{
	//创建一个大顶堆
	creatHeap(array);
	for(int i=array.length-1;i>=0;i--)
	{
		//交换
		int tmp=array[i];
		array[i]=array[0];
		array[0]=tmp;

		//向下调整
		shiftDown(array,0,i);
	}
}
//创建堆:时间复杂度: O(n)
public void createHeap(int [] array)
{
	for(int i=(array.length-1-1)/2;i>=0;i--)
	{
		//向下调整
		shiftDown(array,i,array.length);
	}
}
//向下调整,时间复杂度:O(log2n)
public void shiftDown(int [] array,int root,int len)
{
	int parent=root;
	int child=parent*2+1;
	while(child<=len)
	{
		if(child+1<len && array[child+1]<array[child])
		{
			child=chile+1;
		}
		if(array[child]>array[parent])
		{
			//交换
			int tmp=array[child];
			array[child]=array[parent];
			array[parent]=tmp;

			parent=child;
			child=parent*2+1;
		}else{
			break;
		}	
	}
}

4)时间复杂度(无论有序还是无序): O(n * log2n) 即:循环次数 * 向下调整的时间复杂度 + O(n) <建堆的时间复杂度> ------>把低次幂省略掉:O(n*log2n)

5)空间复杂度:O(1)

6)稳定性:不稳定,因为有跳跃式的交换(第一次就把0号下标和9号下标进行交换)

评价:堆排序的时间复杂度是比较高的,唯一的缺点就是不稳定

你可能感兴趣的:(数据结构,排序算法,算法,数据结构)