Java中常用的数组排序算法

Java中常用的数组排序算法

01-排序概述与数据换位
1.排序
  • 被排序数据是一组相同类型、相同定位的数据,数组表示的就是这样的数据
  • 核心:“对比"和"换位”
    • 根据“对比”的标准不同,可产生“升序”和“降序”结果
    • 在某些特殊的排序算法中可能没有“换位”操作,详见后续各算法原理
2.数据换位
  • 如果只是让2个变量的值分别赋值给彼此,由于程序代码是依次执行的,一旦某个变量被赋予新的值,则其原来的值就会丢失!

  • 为了保证在交换值的过程中,变量的原值不会丢失(被重新赋值导致的覆盖)﹐可以使用第3个变量临时存储变量的原值

  • 也可以不使用临时变量,而使用算术运算来实现换位(代码语义较差)

02-冒泡排序(Bubble sort)
1.原理
  • 反复对比相邻的两个元素,如果与预期的顺序不符,则换位

  • 需要进行多轮循环,可以使用嵌套的循环来实现

  • 外层循环表示循环轮次,数组的长度-1
    初始条件: int i= 0
    循环条件:i< array.length-1

  • 内层循环用于对比和换位
    循环条件:数组长度-当前轮次–1

​ 当前轮次使用0作为初始值来计数

2.实现
package com.tt.test.demo1;

import java.util.Arrays;
import java.util.Random;

public class BubbleSort {
    public static void main(String[] args){
        //方式一:创建需要排序的数组对象
        //int[] array = {8,1,4,9,0,3,5,2,7,6}

        //方式二:使用随机数生成的数组
        //随机数工具对象
        Random random = new Random();
        //设置生成的随机的数值大小上限(不含此值)
        int numberBound = 100;
        //设置生成随机数的数量,也是数组的长度
        int numbersCount = 50;

        //创建数组
        int[] array = new int[numbersCount];
        //遍历数组
        for(int i = 0; i < array.length; i++){
            //生成随机数,作为当前遍历到的数组元素的值
            array[i] = random.nextInt(numberBound);
        }
        //输出显示生成的数组
        System.out.println(Arrays.toString(array));

        //记录排序之前的时间值
        long startTime = System.currentTimeMillis();

        //-------冒泡排序开始-------
        //外层的循环
        for(int i = 0; i< array.length - 1; i++){
            //遍历数组
            //循环变量j:某元素的下标,将始终与下标为j+1的元素进行对比
            //注意:数组的最右侧元素没有对比对象,所以遍历至array.length-1即可
            //注意∶内层循环的次数是递减的,需要改为j < array.length - 1 - i
            for(int j = 0; j< array.length - 1 - i; j++){
                //如果左侧元素更大,则换位
                if(array[j] > array[j + 1]){
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                }
                //输出显示数组
                //System.out.println(Arrays.toString(array));
            }
           // System.out.println();
        }
        //记录排序之后的时间值
        long endTime = System.currentTimeMillis();

        //输出排序后的数组
        System.out.println(Arrays.toString(array));
        
        //耗时
        System.out.println("耗时" + (endTime - startTime) + "毫秒");

    }
}
03-选择排序(Selection sort)
1.原理
  • 将未排序的第1个数字和剩余的每个数字进行对比,如果与预期的顺序(升序)不符,则换位
  • 需要进行多轮循环,可以使用嵌套的循环来实现
  • 外层循环表示循环轮次,数组的长度- 1
    • 初始条件: int i=0
    • 循环条件:i< array.length - 1
  • 内层循环用于对比和换位
    • 初始条件: int j = i+1
    • 循环条件: j<array.length
2.实现
package com.tt.test.demo1;
import java.util.Arrays;

public class SelectionSort{

    public static void main(String[] args) {

        //创建需要排序的数组对象
        int[] array = {8,1,4,9,0,3,5,2,7,6};

        //新的循环
        for (int i = 0; i < array.length; i++) {
            //遍历数组
            //使用j表示被对比的元素下标
            for (int j = i + 1; j < array.length; j++) {
                if (array[i] > array[j]) {
                    int temp = array[i];
                    array[i] = array[j];
                    array[j] = temp;
                }
                //输出显示数组,观察数组变化
                System.out.println(Arrays.toString(array));
            }
            System.out.println();
        }
    }

}
04-插入排序(Insertion sort)
1.原理

将第一个元素作为起始数组,不管这个元素是大还是小。然后用第二个元素与这个元素进行比较,如果比起始元素大,则放在起始元素之后,反之放在之前,组成一个有序集合,然后用其他元素跟有序集合中的元素比较,插入到正确的位置,以此类推达到有序。

2. 实现
package com.tt.test.demo1;

import java.util.Arrays;

public class InsertionSort {
    public static void main(String[] args) {
        //创建需要排序的数组对象
        int[] array = {8,1,4,9,0,3,5,2,7,6};

        //从下标为1的元素开始,反复对比左侧元素
        for (int i = 1; i < array.length; i++) {

            //当前需要确定位置的元素的下标,暂时假设是数组的最右侧元素
            int j = i;
            //当j > 0时循环
            //判断j指向的元素与其左侧元素的大小
            while (j > 0 && array[j] < array[j - 1]) {
                //当左侧元素j-1更大时,执行换位
                int t = array[j];
                array[j] = array[j - 1];
                array[j - 1] = t;

                //j自减,表示向左移动
                j--;

            }
        }
        System.out.println(Arrays.toString(array));

    }
}

05-希尔排序(Shell sort)
1.介绍
  • 希尔排序(Shell sort)的名称源自于它的发明者Donald Shello
  • 它通过相距一定间隔的元素来工作,各轮对比所用的距离随着算法的进行而减小,走到只比较相邻元素的最后一轮排序为止,所以,它也叫作缩减增量排序(Diminishing increment sort)。
  • 从某一程度来看,它是插入排序的升级版。
2.原理

​ 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止 。

3.实现
package com.tt.test.demo1;

import java.util.Arrays;

public class ShellSort {
    public static void main(String[] args) {
        int[] arr = {8,1,4,9,0,3,5,2,7,6};

//        ShellSort_swap(arr);
        ShellSort_move(arr);

    }


    //交换式
    public static void ShellSort_swap(int[] arr) {
        int temp = 0;
        //多轮循环:缩减增量
		//初始条件:增量(gap)值为数组长度除以2
        //循环条件:增量>0
		//条件自变:增量自除2
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            //从与增量值大小相等的下标位置开始,向右循环
            for (int i = gap; i < arr.length; i++) {
                //步长为5(每组有两个元素)
                //接下来的循环表示向左找"同组"的元素尝试对比及必要的换位
                //j:左侧"同组"元素的下标,即被对比元素的下标
                for (int j = i - gap; j >= 0; j -= gap) {
                    //如果当前元素大于加上步长后的那个元素,则交换
                    if (arr[j] > arr[j + gap]) {
                        temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
        }
        System.out.println("Shell排序后:" + Arrays.toString(arr));
    }


    //移位式的希尔排序
    public static void ShellSort_move(int[] arr){
        //增量的gap
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            int temp = 0;
            int j = 0;
            //从第gap个元素开始,逐个对其所在的组进行直接插入
            for(int i = gap ; i < arr.length ; i++){
                j = i;
                temp = arr[j];
                if(arr[j] < arr[j - gap]){
                    while (j - gap >= 0 && temp < arr[j - gap]){
                        //移动
                        arr[j] = arr[j - gap];
                        j -= gap;
                    }
                    //当退出while循环后,就给temp找到了插入位置
                    arr[j] = temp;
                }
            }
        }
        System.out.printf("Shell排序后:%s\n", Arrays.toString(arr));
    }
}
06-归并排序(Merge sort)
1.介绍

​ 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)

​ 分治策略是一种非常经典的思想,它将问题分(divide)成一些小问题,然后递归求解,而治(conquer)的阶段会将分的阶段得到的问题解合并在一起。

​ 递归:表现为在方法的内部,调用当前方法自身

2.原理

​ 归并排序的核心思想是将2个未排序的“小”数组的所有元素有序的填充到一个新的数组中去,排序的同时形成“合并”。

3.实现
package com.tt.test.demo1;
import java.util.Arrays;
public class mergeSort {
	public static void main(String[] args) {
		int[] array = {8, 1, 4, 9, 0, 3, 5, 2, 7, 6};
		int[] temp = new int[array.length]; // 归并排序需要一个额外空间
		merges(array, 0, array.length - 1, temp);
		System.out.println(Arrays.toString(array));
	}
	//分+合的方法
	public static void merges(int[] array, int left, int right, int temp[]) {
		if (left < right) {
			int mid = (left + right) / 2;
			// 中间索引
			// 向左递归进行分解
			merges(array, left, mid, temp);
			// 向右递归进行分解
			merges(array, mid + 1, right, temp);
			// 合并
			merge(array, left, mid, right, temp);
		}
	}
	// 合并方法
	// array,排序数组;left,左边有序序列的初始索引;right,右边有序序列的初始索引;mid,中间索引;temp,做中转的数组
	public static void merge(int array[], int left, int mid, int right, int temp[]) {
		int i = left;
		int j = mid + 1;
		int t = 0;// t表示temp中转数组的初始索引
		// 第一步:先把左右两边有序的数据按照规则填充到temp数组,直到有一边处理完毕为止
		while (i <= mid && j <= right) {
			if (array[i] < array[j]) {
				temp[t] = array[i];
				i += 1;
				t += 1;
			} else {
				temp[t] = array[j];
				j += 1;
				t += 1;
			}
		}
		// 第二步:把剩余数据的一边的数据依次全部填充到temp
		while (i <= mid) {
			temp[t] = array[i];
			i += 1;
			t += 1;
		}
		while (j <= right) {
			temp[t] = array[j];
			j += 1;
			t += 1;
		}
		// 第三步:将temp数组的元素拷贝到array
		// 将 temp 数组的元素拷贝到 arr
		// 注意,并不是每次都拷贝所有 t = 0; int tempLeft = left; //
		// 第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3 //最后一次
		// tempLeft = 0 right = 7
		t = 0;
		int tempLeft = left;
		while (tempLeft <= right) {
			array[tempLeft] = temp[t];
			t += 1;
			tempLeft += 1;
		}
	}
}
07-快速排序(Quick sort)
1.原理
  • 先挑选数组中的某个元素,它将作为所有元素排列大小的分界值

  • 作为分界值的数组元素称之为:枢纽元 (pivot),也可称之为:主元

  • 需要将比枢纽元小的元素放在其左侧位置,将比枢纽元大的元素放在其右侧位置。并不关心其左侧区域或右侧区域内的各元素是否有序

  • 将原数组根据枢纽元划分开来的过程称之为:分区(Partition)

    先从一堆数据中挑选出一个基准数,然后将比这个基准数小的数据全部放在基准数的前面,将比他大的放在基准数后面,此时,不管前后,都是无序的,然后再分别在前后两组数据中挑出一个基准数,重复此操作,一直分下去,分成两部分、四部分、八部分。。。直到每一个数据的左边都比他小,右边都比他大,此时整个数组就是有序的了。

​ 常用的选取枢纽元的方案。

​ 错误的做法

​ 选取两端的某个元素作为枢纽元,在有序甚至降序的数组中,表现非常糟糕
有序或局部有序的原始数组并不罕见
安全的做法
随机选取枢纽元
因为几乎不可能每次都产生劣质的分区
但是,生成随机数也是有开销的,可能导致排序效率下降

​ 三数中值分割法(Median-of-Three Partitioning)

​ 使用最左侧、最右侧、中心位置的三个元素的中值作为枢纽

​ 可以消除有序数组的坏情况

2.实现
public static void QuickSort(int[] arr, int start,int end ){
        if(start<end){
            int index=getIndex(arr,start,end);//定义getIndex方法传入参数
 
            QuickSort(arr,start,index-1);//使用递归的方法,对基准数左边部分进行递归
 
            QuickSort(arr, index+1, end);//对右边部分递归
        }
    }
 
    private static int getIndex(int[] arr, int start, int end) {
        int i=start;
        int j=end;
        //定义一个基准数
        int x=arr[i];
        while (i<j){
            //从后往前找比他小的数的下标。
            while (i < j&&arr[j]>x) {
                j--;              //如果比他大则向前移动,直到找到小的为止。
            }
            //将找到的数填到上一个数的位置。
            if (i < j) {
                arr[i] = arr[j];
                i++;
            }
            //从前往后找比他大于等于的数的下标。
            while (i < j && arr[i]<= x) {
                i++;
            }
            if (i < j) {
                arr[j] = arr[i];
                j--;
            }
        }
 
        arr[i]=x;
        return i;
 
    }
08_排序算法的选取
  • 在一般情况下,可能比较关注算法所表现出来的运算效率,评估值主要包括:
    • 判断次数
    • 交换次数
    • 耗时
  • 可以随机生成50000个随机数构成的数组,并观察以上各指标。

注意:

  • 一般不建议使用元素特别多的数组,因为有些算法的性能非常差;

    ​ 一例如,使用冒泡排序来处理长度为50万的数组,整个排序过程可能需要7分钟以上(仅供参考)

  • 由于随机数值大小、初始数组内元素的大小顺序、机器的性能等多方面原因,测试结果仅供参考;

    ​ 一例如,在快速排序时,数组元素的顺序直接影响排序效率,另外,插入排序、希尔排序、归并排序也是这样

  • 归并排序没有发生实质的交换,而是将原数组的元素有序的填充到新数组,只能使用“填充次数”替代“交换次数”作为参考。

  • 另外,还必须关注算法的稳定性!

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值