常用的排序方法(Java实现)(一)

常用的排序(一)

author: EverKnows
date:2019-3-26

KeyWord:Java Sort
部分代码借鉴《大话数据结构》


1.冒泡排序(Bubble Sort)

冒泡排序通过两两比较的方式,从待排数组的最后一个元素开始,将相邻元素的较小值不断向前传递,就像泡沫从水底升起一样,将最小值放到待排数组的开头;第二趟排序也是从最后一个元素开始,将数组中第二小的元素交换到数组第二的位置,以此类推。

冒泡排序过程(图片素材来自《大话数据结构》):

代码:

for (int i = 0;i < arr.length;++i) {  // arr.length个数字,所以进行arr.length次排序
	for (int j = arr.length - 1;j > i; --j) { // 前i+1个数字已经有序,故j>i
		if (arr[j] < arr[j - 1]) {
			swap(arr, j, j -1);
		}
	}
}

优化:

在完成冒泡排序后,我们应该注意到数组的有序性问题,若待排数组本身有序性较高,则在第arr.length-1趟排序前,待排数组已经有序。故在排序的过程中,我们需要时刻注意数组是否已经完成排序。根据我们排序的原理,若数组已经有序,则在冒泡的过程中不会发生元素的交换,根据这一点我们设立一个Flag来记录元素是否发生交换,代码如下:

优化后的代码:


boolean Flag = false;
for (int i = 0;i < arr.length - 1;++i) {  // arr.length个数字,所以进行arr.length -1次排序
	Flag = false;  // 在每一次排序前都重新初始化标记
	for (int j = arr.length - 1;j > i; --j) { // 前i+1个数字已经有序,故j>i
		if (arr[j] < arr[j - 1]) {
			Flag = true;  //发生交换,将Flag置为true
			swap(arr, j, j -1);
		}
	}
	if (!Flag) {   // 如果Flag == false,说明冒泡过程中并没有发生交换,故数组已经完成排序
		break;  // 退出循环
	} 
}

时间复杂度:

平均情况最好情况最坏情况
O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2)
  • 最好情况: 数组有序,则在第一趟冒泡完成后就完成了排序
  • 最坏情况: 数组逆序

2.简单选择排序(Select Sort)

选择排序在我看来和与冒泡排序有异曲同工之处,根据选择排序的原理,它的每一趟排序同样是将数组中的最小值放在数组的最前面,无非是比较的方式由冒泡排序的相邻元素的比较替换成了第i个元素和其后每个元素的比较。

代码:

for (int i = 0;i < arr.length - 2;++i) { 
	min = i;
	for (int j = i + 1;j < arr.length;++j) {
		if (arr[min] > arr[j]) {
			min = j;
		}
	}
	if (min != i) 
		swap(arr, min, i);
}

时间复杂度:

平均情况最好情况最坏情况
O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2)

3.直接插入排序 (Insetrt Sort)

插入排序就像玩扑克牌时整理你的手牌一样,首先你以第一张牌为基准,从而决定第二张牌是否应该插入第一张牌的位置,然后同理,根据第二张牌,你将决定第三张牌应该插入的位置,以此类推,当你完成最后一张牌的顺序调整后,排序也就完成了。

插入排序过程(图片来自Danny_姜):
在这里插入图片描述

代码:

for (int i = 1;i < arr.length;++i) {  // 从第二个元素开始,因为第一个元素已经有序
	int temp = arr[i];  // 记录当前元素的位置,方便比较
	for (int j = i - 1;j >= 0 && arr[j] > temp;--j) { // 这里需要注意数组不能越界,同时要找到arr[i]元素将要将插入的位置
		arr[j + 1] = arr[j];   // 元素整体后移
	}
	swap(arr, i, j + 1);  // 将arr[i]元素插入到正确的位置
}

时间复杂度:

平均情况最好情况最坏情况
O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( n 2 ) O(n^2) O(n2)
  • 最好情况: 数组有序,则在第一趟冒泡完成后就完成了排序
  • 最坏情况: 数组逆序

4.快速排序 (Quick Sort)

快速排序的基本思想是:通过找出一个基准值将待排序分裂成两个子序列,一个子序列的值全部小于基准值,另一个则大于基准值,然后对分裂出来的序列重复进行以上操作,就达到了排序的目的。

代码:

public void QuickSort(int[] arr, int left, int right) {
	if (left < right) {
		int pivotKey = getPivotKey(arr, left, right); // 找到基准值的下标
		QuickSort(arr, left,pivotKey);  // 对前半部分继续进行排序
		QuickSort(arr, pivotKey + 1, right);
	}
}

public int getPivotKey(int[] arr, int left, int right) {
	int baseNum = arr[left];  // 将序列的第一个值作为基准值
	while (left <right) {  // 
		while (arr[right] >= baseNum && left < right)  // 从右边开始找,因为我们将左边定义为了基准值
			--right;
		swap(arr, left, right);   // 若找到小于基准值的元素,将之与左下标所指元素交换,同时基准值被交换到右下标处
		while(arr[left] >= baseNum && left < right) 
		  ++left;
		swap(arr, left, right);
	}
	return left;  // 此时left == right,即基准值元素的下标
}

**此处未找到合适的图片= = **

优化:

  • 基准值的选择上的优化:
    1.可以通过三数取中法来选择基准值,代码如下:
    2.可以通过九数取中法
int mid = (right + left) / 2 ;
if (arr[left] > arr[right]) {   // 保证右值大于左值
	swap(arr , left, right);
}
if (arr[mid] > arr[right]) {   // 保证右值大于中值
	swap(arr, mid, right)
}
if (arr[m] > arr[left]) {   // 保证左值大于中值
	swap(arr, mid, left);
}
pivotKey = arr[left];
  • 排序方法选择上的优化:
  1. 在待排序列小于一定长度时,采用简单排序中的直接插入排序
public void QuickSort(int[] arr, int left, int right) {
	if (right - left + 1 < MIN_LENGTH_INSERT_SORT) {
		if (left < right) {
			int pivotKey = getPivotKey(arr, left, right); // 找到基准值的下标
			QuickSort(arr, left,pivotKey);  // 对前半部分继续进行排序
			QuickSort(arr, pivotKey + 1, right);
		}
	} else {
		InsertSort(arr, left, right);
	}
}
  • 交换次数上的优化:
    1.代码如下:
public int getPivotKey(int[] arr, int left, int right) {
	int baseNum = arr[left];  // 将序列的第一个值作为基准值
	while (left <right) {  // 
		while (arr[right] >= baseNum && left < right)  // 从右边开始找,因为我们将左边定义为了基准值
			--right;
		arr[left] = arr[right];   // 若找到小于基准值的元素,将之与左下标所指元素交换,同时基准值被交换到右下标处
		while(arr[left] >= baseNum && left < right) 
		  ++left;
		arr[right] = arr[left];	
	}
	arr[left] = baseNum;
	return left;  // 此时left == right,即基准值元素的下标
  • 递归操作上的优化:
    1.采用尾递归,代码如下:
public void QuickSort(int[] arr, int left, int right) {
	while (left < right) {
		int pivotKey = getPivotKey(arr, left, right); // 找到基准值的下标
		QuickSort(arr, left,pivotKey);  // 对前半部分继续进行排序
		low = pivotKey + 1;
	}
}

时间复杂度:

平均情况最好情况最坏情况
O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n)) O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n)) O ( n 2 ) O(n^2) O(n2)

5.归并排序 (Merge Sort)

归并排序的基本思想:将待排序序列分解成独立的元素,在将由同一个序列分裂而来的两个元素进行合并,合并完成后,排序就完成了。

代码:

public void MSort(arr, int left, int right) {
	int[] TR1 = new int[right - left + 1];
	Sort(arr, TR1, left, right);
	System.arraycopy(TR1, 0, arr, 0, arr.length);
}

public void Sort(int[] SR, int[] TR1, int left, int right) {
	int[] TR2 = new int[SR.length];
	if (s == t) {
		TR2[s] = SR[s];
	} else {
		int mid = (left + right) / 2;   // 从中间将数组分为两个子序列
		Sort(SR, TR2, left, mid);    // 对前半部分序列进行分裂操作
		Sort(SR, TR2, mid + 1, right);
		Merge(TR2, TR1, left, mid, right);  // 将由同一个子序列分裂的两个子序列进行合并
	}
}

public void Merge(int[] TR2, int[] TR1, int left, int mid, int right) {
	int s = left;
	int k = s;
	int t = mid + 1;
	while(s <= mid && t <= right) {   // 排序操作
		if (TR2[s] < TR2[t]) {
			TR1[k++] = TR2[s++];
		} else {
			TR1[k++] = TR2[t++];
		}
	}
	while (s <= mid)   // 若某个序列没有完成并入TR1,则继续进行归并
		TR1[k++] = TR2[s++];
	while (t <= right) 
		TR1[k++] = TR2[t++];
}

归并排序(来源于百度图片):
在这里插入图片描述

归并排序的非递归方式:

使用非递归归并排序的原因:递归的方式过于消耗空间,采用非递归方式可以有效的减少空间上的损耗!

代码:

public void MergeSort(int[] arr, int left, int right) {
	int[] TR1 = new int[arr.length];
	while (k <= arr.length) {
		MergePass(arr, TR1, k);   // 将部分有序的arr归并到TR1
		k *= 2;
		MergePass(TR1, arr, k);   // 将部分有序的TR1归并到arr
		k *= 2;
	}
}

public void MergePass(int[] arr, int[] TR1, int s) {
	int i = 0;
	while (i < arr.length - 2 * s + 1) {  // 两两归并
		Merge(arr, TR1, i, i + s - 1, i + 2 * s - 1);
		i = i + 2 * s;
	}
	if (i < arr.length - s + 1) {  // 若最后尾部剩下一个长度为s的序列和一个长度不足s的序列
		Merge(arr, TR1, i, i - s + 1, arr.length);
	} else {
		for (int j = i;j < arr.length;j++) 
			TR1[j] = arr[j];
	}
}


public void Merge(int[] TR2, int[] TR1, int left, int mid, int right) {
	int s = left;
	int k = s;
	int t = mid + 1;
	while(s <= mid && t <= right) {   // 排序操作
		if (TR2[s] < TR2[t]) {
			TR1[k++] = TR2[s++];
		} else {
			TR1[k++] = TR2[t++];
		}
	}
	while (s <= mid)   // 若某个序列没有完成并入TR1,则继续进行归并
		TR1[k++] = TR2[s++];
	while (t <= right) 
		TR1[k++] = TR2[t++];
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值