交换排序
一、冒泡排序
冒泡排序是排序算法中较为简单的一种,英文称为Bubble Sort。
它遍历所有的数据,每次对相邻元素进行两两比较,如果顺序和预先规定的顺序不一致,则进行位置交换;这样一次遍历会将最大或最小的数据上浮到顶端,之后再重复同样的操作,直到所有的数据有序。
有n个数据,需要n - 1趟比较,
时间复杂度 O(n2),空间复杂度O(1)
以升序冒泡排序为例,冒泡排序就是要**每趟排序过程中通过两两比较相邻元素,将小的数字放到前面,大的数字放在后面。**代码如下:
package com.wang;
/**
* @Author Mickey
* @PackageName DailyTest
* @Package com.wang
* @Date 2022/5/12 15:23
* @Version 1.0
*/
public class BubbleSort {
public static int[] bubbleSort(int[] nums){
int n = nums.length;
for(int i = 1;i < n;i++){
boolean flag = true;
for(int j = 1;j < n;j++){
if(nums[j] < nums[j - 1]){
int temp = nums[j - 1];
nums[j - 1] = nums[j];
nums[j] = temp;
flag = false;
}
}
//如果为true,则说明排序已经提前完成
if(flag){
break;
}
}
return nums;
}
public static void display(int [] array){
for(int i = 0 ; i < array.length ; i++){
System.out.print(array[i]+" ");
}
System.out.println();
}
public static void main(String[] args) {
int[] nums = new int[]{4,2,8,9,5,7,6,3,1};
System.out.println("未排序数组顺序为:");
display(nums);
System.out.println("-----------------------");
nums = bubbleSort(nums);
System.out.println("-----------------------");
System.out.println("经过冒泡排序后的数组顺序为:");
display(nums);
}
}
性能:
算法稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
冒泡排序就是把小的元素往前调或者把大的元素往后调。是相邻的两个元素的比较,交换也发生在这两个元素之间。所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
二、快速排序
快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数。
然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
package com.wang;
/**
* @Author Mickey
* @PackageName DailyTest
* @Package com.wang
* @Date 2022/5/12 15:39
* @Version 1.0
*/
public class QuickSort {
public static void quickSort(int[] arr,int low,int high){
if(low>high){
return;
}
int i = low;int j = high;
//base就是基准位
int base = arr[low];
while (i<j) {
//先看右边,依次往左递减
while (base <= arr[j] && i < j) {
j--;
}
//再看左边,依次往右递增
while (base >= arr[i] && i < j) {
i++;
}
//如果满足条件则交换
if (i < j) {
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
//最后将基准位与i和j相等位置的数字交换
arr[low] = arr[i];
arr[i] = base;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
public static void main(String[] args){
int[] arr = {10,7,2,4,7,62,3,4,2,1,8,9,19};
quickSort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
性能:
插入排序
三、希尔排序
希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。希尔排序又称缩小增量排序,因 DL.Shell 于 1959 年提出而得名。它通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。
希尔排序时间复杂度是 O(n^(1.3-2)),空间复杂度为常数阶 O(1)。希尔排序没有时间复杂度为 O(n(logn)) 的快速排序算法快 ,因此对中等大小规模表现良好,但对规模非常大的数据排序不是最优选择,总之比一般 O(n^2 ) 复杂度的算法快得多。
三、过程图示
希尔排序目的为了加快速度改进了插入排序,交换不相邻的元素对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。
在此我们选择增量 gap=length/2,缩小增量以 gap = gap/2 的方式,用序列 {n/2,(n/2)/2…1} 来表示。
(1)初始增量第一趟 gap = length/2 = 4
(2)第二趟,增量缩小为 2
(3)第三趟,增量缩小为 1,得到最终排序结果
package com.wang;
import java.util.Arrays;
/**
* @Author Mickey
* @PackageName leetcodeTest
* @Package com.wang
* @Date 2022/5/15 20:21
* @Version 1.0
*/
public class ShellSort {
public static void shellSort1(int[] array){
int insertValue = 0;//保存待插入的值
int j = 0;//记录要插入的位置下标
int count = 0;//记录比较轮次
for (int gap = array.length/2;gap >0; gap/=2) {//每次分组的步长(增量),每次增量除以2
for (int i = gap; i <array.length ; i++) {
j = i;
insertValue = array[j];//保存待插入的值
if (array[j]<array[j-gap]){//要插入的值小于前面有序子序列的尾元素就进行元素移动
while (j-gap>=0&&insertValue<array[j-gap]){
array[j] = array[j-gap];
j-=gap;
}
array[j] = insertValue;
}
}
System.out.println("第"+(++count)+"轮希尔排序结果:"+ Arrays.toString(array));
}
}
public static void main(String[] args) {
int[] array = {8,9,1,7,2,3,5,4,6,0};
ShellSort.shellSort1(array);
}
}
四、直接插入排序
直接插入排序基本思想是每一步将一个待排序的记录,插入到前面已经排好序的有序序列中去,直到插完所有元素为止。
package com.wang;
import java.util.Arrays;
/**
* @Author Mickey
* @PackageName leetcodeTest
* @Package com.wang
* @Date 2022/5/15 18:15
* @Version 1.0
*/
public class InsertSort {
public static void insertSort(int[] nums){
int temp,j;
for(int i = 0;i < nums.length;i++){
//记录要插入的数据
temp = nums[i];
//从右向左在有序区[0,i-1]中找nums[i]插入的位置
j = i - 1;
while(j >= 0 && temp < nums[j]){
//将大于nums[i]的元素后移
nums[j + 1] = nums[j];
j--;
}
//在j + 1出插入nums[i]
nums[j + 1] = temp;
}
}
public static void main(String[] args) {
int[] nums = {2,1,6,4,7,9,4,5,8};
System.out.println("排序前:");
Arrays.stream(nums).forEach(System.out::println);
insertSort(nums);
System.out.println("排序后:");
Arrays.stream(nums).forEach(System.out::println);
}
}
选择排序
选择排序:每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止。
五、简单选择排序
选择排序的基本思想描述为:每一趟在n-i+1(i=1,2,…,n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。具体来说,假设长度为n的数组arr,要按照从小到大排序,那么先从n个数字中找到最小值min1,如果最小值min1的位置不在数组的最左端(也就是min1不等于arr[0]),则将最小值min1和arr[0]交换,接着在剩下的n-1个数字中找到最小值min2,如果最小值min2不等于arr[1],则交换这两个数字,依次类推,直到数组arr有序排列。算法的时间复杂度为O(n^2)。
package com.wang;
import java.util.Arrays;
/**
* @Author Mickey
* @PackageName leetcodeTest
* @Package com.wang
* @Date 2022/5/15 17:10
* @Version 1.0
*/
public class SelectSort {
public static int[] selectSort(int[] nums){
int index = 0;
for(int i = 0;i < nums.length;i++){
index = i;
for(int j = i + 1;j < nums.length;j++){
if(nums[j] < nums[index]){
index = j;
}
}
if(index != i){
int temp = nums[i];
nums[i] = nums[index];
nums[index] = temp;
}
}
return nums;
}
public static void main(String[] args) {
int[] nums = new int[]{1,3,2,5,6,8,6};
int[] res = selectSort(nums);
Arrays.stream(res).forEach(System.out::println);
}
}
六、堆排序
堆是一棵顺序存储的完全二叉树。
- 其中每个结点的关键字都不大于其孩子结点的关键字,这样的堆称为小根堆。在堆排序算法中用于升序排列;
- 其中每个结点的关键字都不小于其孩子结点的关键字,这样的堆称为大****根堆。在堆排序算法中用于降序排列;
举例来说,对于n个元素的序列{R0, R1, … , Rn}当且仅当满足下列关系之一时,称之为堆:
- Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)
- Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)
其中i=1,2,…,n/2向下取整;
用到完全二叉树的性质如下:
- (1)最后一个非叶子节点的位置为:
n / 2 - 1
,n 为完全二叉树的节点总数 - (2)从 0 开始计数,第 i 个节点的左孩子的位置为:
2 * n + 1
,右孩子位置为:2 * n + 2
堆排序算法的基本思路:
- 将无序序列建成一个堆,根据升降序需求选择大顶堆和小顶堆。
- 将堆顶元素与末尾元素交换,将最大元素沉到数组末端。
- 重新调整,使其重新满足堆定义,然后继续交换堆顶元素和当前末尾元素,反复执行调整和交换,直至整个序列有序。
package com.wang;
import java.util.Arrays;
/**
* @Author Mickey
* @PackageName leetcodeTest
* @Package com.wang
* @Date 2022/5/15 20:39
* @Version 1.0
*/
public class HeapSort {
public static void heapSort(int[] array)
{
//从倒数第一个非叶子节点开始
for (int i = array.length/2 - 1; i>=0; i--)
{
//从第一天非叶子节点从下至上,从左至右调整结构
adjustHeap(array,i, array.length);
}
//将堆顶元素与末尾元素交换 将最大元素沉到数组末尾 + 重新调整堆结构
for (int i = array.length - 1; i > 0 ; i--)
{
//交换堆顶元素和末尾元素
swap(array,0,i);
//交换后的末尾元素忽略(j--) 不再参与堆结构的调整
//重新调整堆结构
adjustHeap(array,0,i);
}
}
private static void swap(int[] array, int start, int end)
{
int temp = array[start];
array[start] = array[end];
array[end] = temp;
}
public static void adjustHeap(int[] array,int index,int length)
{
//取出当前元素
int temp = array[index];
//i节点是index节点的左子节点
for (int i = 2 * index + 1; i < length; i = 2 * i + 1)
{
//表明左子节点小于右子节点
if (i+1 < length && array[i] < array[i+1])
{
//将指针移至较大节点
i++;
}
//如果子节点大于父节点
if (array[i] > temp)
{
//将较大值赋给当前节点
array[index] = array[i];
//指针移向子节点
index = i;
}
else
{
break;
}
}
//循环结束,已经将最大值放在了堆顶
//将temp值放到最终的位置
array[index] = temp;
}
public static void main(String[] args)
{
//升序
int[] array = {4,6,8,78,13,19,1,5,9,};
System.out.println("排序前=>"+ Arrays.toString(array));
heapSort(array);
System.out.println("排序后=>"+ Arrays.toString(array));
}
}
七、归并排序
基本思想:假设初始序列含有n个记录,则可看成时n个有序的子序列,每个子序列的长度为1,然后两两并归,得到n/2个长度为2或1的有序子序列;如此重复,直至得到一个长度为n的有序序列为止。这种排序方法称为2-路归并排序。
先从上到下分解,在从下到上排序
package com.wang;
/**
* @Author Mickey
* @PackageName leetcodeTest
* @Package com.wang
* @Date 2022/5/15 17:29
* @Version 1.0
*/
import java.util.Arrays;
public class MergeSort{
public static int[] sort(int[] nums,int low,int high){
if(low < high){
int mid = low + (high - low) / 2;
// 左边归并排序
sort(nums,low,mid);
// 右边归并排序
sort(nums,mid + 1,high);
//合并两个有序数组
merge(nums,low,mid,high);
}
return nums;
}
private static void merge(int[] nums, int low, int mid, int high) {
int[] temp = new int[high - low + 1];
int i = low,j = mid + 1,index = 0;
while (i <= mid && j <= high){
// 对比大小,调整顺序
if(nums[i] < nums[j]){
temp[index++] = nums[i++];
}else{
temp[index++] = nums[j++];
}
}
// 右边剩余元素填充进temp中(因为前面已经归并,剩余的元素必会小于右边剩余的元素)
while(i <= mid){
temp[index++] = nums[i++];
}
// 右边剩余元素填充进temp中(因为前面已经归并,剩余的元素必会大于于左边剩余的元素)
while(j <= high){
temp[index++] = nums[j++];
}
// 调整数组顺序
for(int k = 0;k < temp.length;k++){
nums[k + low] = temp[k];
}
}
public static void main(String[] args) {
int[] a = {1,3,2,5,6,8,6};
sort(a, 0, a.length -1);
Arrays.stream(a).forEach(System.out::println);
}
}
八、桶排序
桶排序是计数排序的升级版。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序。桶排序利用了函数的映射关系把数据分配到桶里,高效与否的关键就在于这个映射函数的确定。
算法思路
根据待排序集合中的数据,确定映射规则和桶的数量;
遍历待排序集合,将每一个元素根据映射规则,移动到对应的桶中;
对每一个桶中元素进行排序。
*依次输出每个桶中的数据,得到整个有序的集合。
步骤:
1.找出待排序数组中的最大值max、最小值min
2.桶的数量为(max-min)/bucketSize+1 ,若没有指定桶的数量为(max-min)/arr.length+1
3.遍历数组 arr,计算每个元素 arr[i] 放的桶存放桶的下标=(arr[i]-min)/arr.lengh
4.每个桶各自排序
5.遍历桶数组,把排序好的元素放进输出数组
package com.wang;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author Mickey
* @PackageName leetcodeTest
* @Package com.wang
* @Date 2022/5/15 19:57
* @Version 1.0
*/
public class BucketSort {
public static final int[] ARRAY = {35, 23, 48, 9, 16, 24, 5, 11, 32, 17};
/* bucketSize 作为每个桶所能放置多少个不同数值,即数值的类型
* 例如当BucketSize==5时,该桶可以存放{1,2,3,4,5}这几种数字,
* 但是容量不限,即可以存放100个3
*/
public static List<Integer> sort(List<Integer> array, int bucketSize) {
if (array == null || array.size() < 2)
return array;
int max = array.get(0), min = array.get(0);
// 找到最大值最小值
for (int i = 0; i < array.size(); i++) {
if (array.get(i) > max)
max = array.get(i);
if (array.get(i) < min)
min = array.get(i);
}
//获取桶的数量
int bucketCount = (max - min) / bucketSize + 1;
//构建桶,初始化
List<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketCount);
List<Integer> resultArr = new ArrayList<>();
for (int i = 0; i < bucketCount; i++) {
bucketArr.add(new ArrayList<>());
}
//将原数组的数据分配到桶中
for (int i = 0; i < array.size(); i++) {
//区间范围
bucketArr.get((array.get(i) - min) / bucketSize).add(array.get(i));
}
for (int i = 0; i < bucketCount; i++) {
if (bucketSize == 1) {
for (int j = 0; j < bucketArr.get(i).size(); j++)
resultArr.add(bucketArr.get(i).get(j));
} else {
if (bucketCount == 1)
bucketSize--;
//对桶中的数据再次用桶进行排序
List<Integer> temp = sort(bucketArr.get(i), bucketSize);
for (int j = 0; j < temp.size(); j++)
resultArr.add(temp.get(j));
}
}
return resultArr;
}
public static void print(List<Integer> array) {
for (int i : array) {
System.out.print(i + " ");
}
System.out.println("");
}
public static void main(String[] args) {
print(Arrays.stream(ARRAY).boxed().collect(Collectors.toList()));
System.out.println("============================================");
print(sort(Arrays.stream(ARRAY).boxed().collect(Collectors.toList()), 2));
}
}