排序总结之冒泡排序 + 选择排序 + 插入排序 + 快速排序 + 堆排序

1、冒泡排序(Bubble Sort)

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。 

//冒泡排序的一种写法,小的数字向前冒泡,两两比较两两交换
	public static void bubbleSort(int arr[]) {
		int temp;
		for (int i=0;i<arr.length-1;i++) {
			for(int j = arr.length-1;j>i;j--) {
				if (arr[j]<arr[j-1]) {
					temp =arr[j-1];
					arr[j-1]=arr[j];
					arr[j]=temp;
				}
			}
		}
	}

2、选择排序(Selection Sort)

选择排序(Selection-sort)是一种简单直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 

	//选择排序  选择最小的放到前面来
	//i代表轮数,j在遍历i后面数组元素,k开始指向i(以k所指数作为基础,不断和j指向数比较或交换,使k指向处的数最小)
	public static void slectSort (int arr[]) {
		int temp;
		for (int i=0;i<arr.length-1;i++) {
			int k=i;
			for(int j=i+1;j<=arr.length-1;j++) {
				if (arr[k]>arr[j]) {
					temp =arr[j];
					arr[j]=arr[k];
					arr[k]=temp;
				}
			}
		}
	}
	

3、插入排序(Insertion Sort)

插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

	public static void insertSort(int arr[]) {
		for (int i=1;i<= arr.length-1;i++) {
			int pivot= arr[i];
			int j=i;
			while(j>0 && arr[j-1]>pivot) {
				arr[j] = arr[j-1];
				j--;
			}
			arr[j]=pivot;  //pivot 查到了其合适的位置
		}
	}

堆排序(Heap Sort)是指利用堆这种数据结构所设计的一种排序算法。
因此,学习堆排序之前,有必要了解堆!若读者不熟悉堆,建议先了解(建议可以通过二叉堆左倾堆斜堆二项堆斐波那契堆等文章进行了解),然后再来学习本章。

我们知道,堆分为"最大堆"和"最小堆"。最大堆通常被用来进行"升序"排序,而最小堆通常被用来进行"降序"排序。
鉴于最大堆和最小堆是对称关系,理解其中一种即可。本文将对最大堆实现的升序排序进行详细说明。

最大堆进行升序排序的基本思想:
① 初始化堆:将数列a[1...n]构造成最大堆。
② 交换数据:将a[1]和a[n]交换,使a[n]是a[1...n]中的最大值;然后将a[1...n-1]重新调整为最大堆。 接着,将a[1]和a[n-1]交换,使a[n-1]是a[1...n-1]中的最大值;然后将a[1...n-2]重新调整为最大值。 依次类推,直到整个数列都是有序的。

下面,通过图文来解析堆排序的实现过程。注意实现中用到了"数组实现的二叉堆的性质"。
在第一个元素的索引为 0 的情形中:
性质一:索引为i的左孩子的索引是 (2*i+1);
性质二:索引为i的左孩子的索引是 (2*i+2);
性质三:索引为i的父结点的索引是 floor((i-1)/2);

例如,对于最大堆{110,100,90,40,80,20,60,10,30,50,70}而言:索引为0的左孩子的所有是1;索引为0的右孩子是2;索引为8的父节点是3。

下面演示heap_sort_asc(a, n)对a={20,30,90,40,70,110,60,10,100,50,80}, n=11进行堆排序过程。下面是数组a对应的初始化结构:

1 初始化堆

在堆排序算法中,首先要将待排序的数组转化成二叉堆。
下面演示将数组{20,30,90,40,70,110,60,10,100,50,80}转换为最大堆{110,100,90,40,80,20,60,10,30,50,70}的步骤。

1.1 i=11/2-1,即i=4

上面是maxheap_down(a, 4, 9)调整过程。maxheap_down(a, 4, 9)的作用是将a[4...9]进行下调;a[4]的左孩子是a[9],右孩子是a[10]。调整时,选择左右孩子中较大的一个(即a[10])和a[4]交换。

1.2 i=3

上面是maxheap_down(a, 3, 9)调整过程。maxheap_down(a, 3, 9)的作用是将a[3...9]进行下调;a[3]的左孩子是a[7],右孩子是a[8]。调整时,选择左右孩子中较大的一个(即a[8])和a[4]交换。

1.3 i=2


上面是maxheap_down(a, 2, 9)调整过程。maxheap_down(a, 2, 9)的作用是将a[2...9]进行下调;a[2]的左孩子是a[5],右孩子是a[6]。调整时,选择左右孩子中较大的一个(即a[5])和a[2]交换。

1.4 i=1


上面是maxheap_down(a, 1, 9)调整过程。maxheap_down(a, 1, 9)的作用是将a[1...9]进行下调;a[1]的左孩子是a[3],右孩子是a[4]。调整时,选择左右孩子中较大的一个(即a[3])和a[1]交换。交换之后,a[3]为30,它比它的右孩子a[8]要大,接着,再将它们交换。

1.5 i=0


上面是maxheap_down(a, 0, 9)调整过程。maxheap_down(a, 0, 9)的作用是将a[0...9]进行下调;a[0]的左孩子是a[1],右孩子是a[2]。调整时,选择左右孩子中较大的一个(即a[2])和a[0]交换。交换之后,a[2]为20,它比它的左右孩子要大,选择较大的孩子(即左孩子)和a[2]交换。

调整完毕,就得到了最大堆。此时,数组{20,30,90,40,70,110,60,10,100,50,80}也就变成了{110,100,90,40,80,20,60,10,30,50,70}。

第2部分 交换数据

在将数组转换成最大堆之后,接着要进行交换数据,从而使数组成为一个真正的有序数组。
交换数据部分相对比较简单,下面仅仅给出将最大值放在数组末尾的示意图。

上面是当n=10时,交换数据的示意图。
当n=10时,首先交换a[0]和a[10],使得a[10]是a[0...10]之间的最大值;然后,调整a[0...9]使它称为最大堆。交换之后:a[10]是有序的!
当n=9时, 首先交换a[0]和a[9],使得a[9]是a[0...9]之间的最大值;然后,调整a[0...8]使它称为最大堆。交换之后:a[9...10]是有序的!
...
依此类推,直到a[0...10]是有序的。

堆排序的时间复杂度和稳定性

堆排序时间复杂度
堆排序的时间复杂度是O(N*lgN)。
假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?
堆排序是采用的二叉堆进行排序的,二叉堆就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的定义,它的深度至少是lg(N+1)。最多是多少呢?由于二叉堆是完全二叉树,因此,它的深度最多也不会超过lg(2N)。因此,遍历一趟的时间复杂度是O(N),而遍历次数介于lg(N+1)和lg(2N)之间;因此得出它的时间复杂度是O(N*lgN)。

堆排序稳定性
堆排序是不稳定的算法,它不满足稳定算法的定义。它在交换数据的时候,是比较父结点和子节点之间的数据,所以,即便是存在两个数值相等的兄弟节点,它们的相对顺序在排序也可能发生变化。
算法稳定性 -- 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!

public  static void  MaxHeapSort(int arr[]) {
		buildMaxHeap(arr);
		int temp;
		for (int i= arr.length-1 ; i>0 ;i--) {
			temp =arr[i];
			arr[i] = arr[0];
			arr[0] = temp;
			heapAdjust_max(arr, 0, i-1); //再重新调整大顶堆
		}
		
		
	}
	public static void buildMaxHeap(int arr[]) {
		for(int i=(arr.length/2)-1; i>=0; i--) {
			heapAdjust_max(arr, i, arr.length-1);
		}
	}
	
														//堆调整 确保父节点比子节点大 //end是最后一个节点的标
	public static void heapAdjust_max(int arr[],int start ,int end) {
		int parent= start;
		int leftNode = parent*2+1;
		//int rightNode = parent*2+2;  这里没有太用上右孩子,多用左孩子+1得到右孩子
		
		//循环条件里面的leftnode不再单指左孩子,它是上一轮循环中较大孩子,也就是做了调整的节点,将其作为下一轮调整的父节点
		for( ;leftNode<=end ; parent = leftNode , leftNode=leftNode*2+1) {
			if (leftNode<end && arr[leftNode]<arr[leftNode+1]) {
				leftNode++; //确保leftnode指向最大的
			}
			if (arr[parent]>=arr[leftNode]) {
				break;
			}else {
				int temp =arr[parent];
				arr[parent] = arr[leftNode];
				arr[leftNode] = temp;  //将父节点换成最大的
			}
		}
	}
	
	
	public static void MinHeapSort(int arr[]) {
		buildMinHeap(arr);
		for(int i=arr.length-1;i>0;i--) {
			int temp =arr[i];
			arr[i]=arr[0];
			arr[0]= temp;
			heapAdjust_min(arr, 0, i-1);//除去有序的部分,重新调整小顶堆
		}
	}
	
	public static void buildMinHeap(int arr[]) {
		for(int i=arr.length/2-1;i>=0;i--) {
			heapAdjust_min(arr, i, arr.length-1);
		}
	}
	
	public static void heapAdjust_min(int arr[],int start ,int end) {
		int parent= start;
		int leftNode = parent*2+1;
		//int rightNode = parent*2+2;  这里没有太用上右孩子,多用左孩子+1得到右孩子
		
		//循环条件里面的leftnode不再单指左孩子,它是上一轮循环中较小孩子,也就是做了调整的节点,将其作为下一轮调整的父节点
		for( ;leftNode<=end ; parent = leftNode , leftNode=leftNode*2+1) {
			if (leftNode<end && arr[leftNode]>arr[leftNode+1]) {
				leftNode++; //确保leftnode指向最小的
			}
			if (arr[parent]<=arr[leftNode]) {
				break;
			}else {
				int temp =arr[parent];
				arr[parent] = arr[leftNode];
				arr[leftNode] = temp;  //将父节点换成最小的
			}
		}
	}

猜你喜欢

转载自blog.csdn.net/qq_28619473/article/details/88664967