java中的常见排序算法

冒泡排序

冒泡排序(Bubble Sort)是一种简单的排序算法。它重复访问要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。访问数列的工作是重复地进行直到没有再需要交换的数据,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端,像水中的气泡从水底浮到水面。
在这里插入图片描述
冒泡排序算法的算法过程如下:
①. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
②. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
③. 针对所有的元素重复以上的步骤,除了一轮筛选出的最大元素。
④. 持续每次对越来越少的元素重复上面的步骤①~③,直到没有任何一对数字需要比较。

代码实现

/**
 * 冒泡排序
 * Created by lxd 
 */
public class BubbleSort {
    public static void sort(int[] array) {
        if (array == null || array.length == 0) {
            return;
        }

        int length = array.length;
        //外层:需要length-1次循环比较
        for (int i = 0; i < length - 1; i++) {
            //内层:每次循环需要两两比较的次数,每次比较后,都会将当前最大的数放到最后位置,所以每次比较次数递减一次
            for (int j = 0; j < length - 1 - i; j++) {
                if (array[j] > array[j+1]) {
                    //交换数组array的j和j+1位置的数据
                     int temp = array[j];
                     array[j] = array[j+1];
                     array[j+1] = temp;
                }
            }
        }
    }
}

算法效率
冒泡排序是稳定的排序算法,最容易实现的排序, 最坏的情况是每次都需要交换, 共需 遍历 并交换将近n²/2次, 时间 复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有 缓存 的temp变量需要内存 空间 , 因此空间复杂度为常量O(1)。
在这里插入图片描述

我们从冒泡排序的代码中看到了交换两个数字的方法 swap(int[] array, int i, int j),这里使用了临时变量,而交换数字主要有三种方法,临时变量法、算术法、位运算法。简单记录下

/**
     * 通过临时变量交换数组array的i和j位置的数据
     * @param array 数组
     * @param i 下标i
     * @param j 下标j
     */
    public static void swapByTemp(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    /**
     * 通过算术法交换数组array的i和j位置的数据(有可能溢出)
     * @param array 数组
     * @param i 下标i
     * @param j 下标j
     */
    public static void  swapByArithmetic(int[] array, int i, int j) {
        array[i] = array[i] + array[j];
        array[j] = array[i] - array[j];
        array[i] = array[i] - array[j];
    }


    /**
     * 通过位运算法交换数组array的i和j位置的数据
     * @param array 数组
     * @param i 下标i
     * @param j 下标j
     * 与0异或保留原值
     */
    public static void  swapByBitOperation(int[] array, int i, int j) {
        array[i] = array[i]^array[j];
        array[j] = array[i]^array[j];             //array[i]^array[j]^array[j]=array[i]
        array[i] = array[i]^array[j];              //array[i]^array[j]^array[i]=array[j]
    }

快速排序

快速排序(Quicksort)是对冒泡排序的一种改进,借用了分治的思想,由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以 递归 进行,以此达到整个数据变成有序序列。

算法描述
快速排序使用分治策略来把一个序列( list )分为两个子序列(sub-lists)。步骤为:
①. 从数列中挑出一个元素,称为”基准”(pivot)。
②. 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
③. 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
在这里插入图片描述
①. 挖坑法 用伪代码描述如下:

(1)low = L; high = R; 将基准数挖出形成第一个坑a[low]。

(2)high–,由后向前找比它小的数,找到后挖出此数填前一个坑a[low]中。

(3)low++,由前向后找比它大的数,找到后也挖出此数填到前一个坑a[high]中。

(4)再重复执行②,③二步,直到low==high,将基准数填入a[low]中。

举例说明: 一个无序数组:[4, 3, 7, 5, 10, 9, 1, 6, 8, 2]

(1)随便先挖个坑,就在第一个元素(基准元素)挖坑,挖出来的“萝卜”(第一个元素4)在“篮子”(临时变量)里备用。 挖完之后的数组是这样:[ 坑, 3, 7, 5, 10, 9, 1, 6, 8,2]

(2)挖右坑填左坑:从右边开始,找个比“萝卜”(元素4)小的元素,挖出来,填到前一个坑里面。 填坑之后:[ 2, 3, 7, 5, 10, 9, 1, 6, 8,坑]

(3)挖左坑填右坑:从左边开始,找个比“萝卜”(元素4)大的元素,挖出来,填到右边的坑里面。 填坑之后:[ 2, 3,坑, 5, 10, 9, 1, 6, 8, 7]

(4)挖右坑填左坑:从右边开始,找个比“萝卜”(元素4)小的元素,挖出来,填到前一个坑里面。 填坑之后:[ 2, 3, 1, 5, 10, 9,坑, 6, 8, 7]

(5)挖左坑填右坑:从左边开始,找个比“萝卜”(元素4)大的元素,挖出来,填到右边的坑里面。 填坑之后:[ 2, 3, 1,坑, 10, 9, 5, 6, 8, 7]

(6)挖右坑填左坑:从右边开始,找个比“萝卜”(元素4)小的元素,挖出来,填到前一个坑里面,这一次找坑的过程中,找到了上一次挖的坑了,说明可以停了,用篮子里的的萝卜,把这个坑填了就行了,并且返回这个坑的位置,作为分而治之的中轴线。 填坑之后:[ 2, 3, 1, 4, 10, 9, 5, 6, 8, 7]

上面的步骤中,第2,4, 6其实都是一样的操作,3和5的操作也是一样的,代码如下:

/**
     *  快速排序(挖坑法递归)
     * @param arr   待排序数组
     * @param low   左边界
     * @param high  右边界
     */
    public static void sort(int arr[], int low, int high) {
        if (arr == null || arr.length <= 0) {
            return;
        }
        if (low >= high) {
            return;
        }

        int left = low;
        int right = high;
        int temp = arr[left]; //挖坑1:保存基准的值

        while (left < right) {
            while (left < right && arr[right] >= temp) {
                right--;
            }
            arr[left] = arr[right]; //坑2:从后向前找到比基准小的元素,插入到基准位置坑1中
            while (left < right && arr[left] <= temp) {
                left ++;
            }
            arr[right] = arr[left]; //坑3:从前往后找到比基准大的元素,放到刚才挖的坑2中
        }
        arr[left] = temp; //基准值填补到坑3中,准备分治递归快排
        System.out.println("Sorting: " + Arrays.toString(arr));
        sort(arr, low, left-1);
        sort(arr, left + 1, high);
    }

②. 左右指针法

用伪代码描述如下:

(1)low = L; high = R; 选取a[low]作为关键字记录为 key 。

(2)high–,由后向前找比它小的数

(3)low++,由前向后找比它大的数

(4)交换第(2)、(3)步找到的数

(5)重复(2)、(3),一直往后找,直到left和right相遇,这时将key和a[low]交换位置。

/**
 * 快速排序
 * Created by zhoujunfu on 2018/8/6.
 */
public class QuickSort {
    /**
     * 快速排序(左右指针法)
     * @param arr 待排序数组
     * @param low 左边界
     * @param high 右边界
     */
    public static void sort2(int arr[], int low, int high) {
        if (arr == null || arr.length <= 0) {
            return;
        }
        if (low >= high) {
            return;
        }

        int left = low;
        int right = high;

        int key = arr[left];

        while (left < right) {
            while (left < right && arr[right] >= key) {
                right--;
            }
            while (left < right && arr[left] <= key) {
                left++;
            }
            if (left < right) {
                swap(arr, left, right);
            }
        }
        swap(arr, low, left);
        System.out.println("Sorting: " + Arrays.toString(arr));
        sort2(arr, low, left - 1);
        sort2(arr, left + 1, high);
    }

    public static void swap(int arr[], int low, int high) {
        int tmp = arr[low];
        arr[low] = arr[high];
        arr[high] = tmp;
    }
}

算法效率
快速排序并不稳定,快速排序每次交换的元素都有可能不是相邻的, 因此它有可能打破原来值为相同的元素之间的顺序。
在这里插入图片描述

直接插入排序

在这里插入图片描述
算法描述
具体算法描述如下:
①. 从第一个元素开始,该元素可以认为已经被排序

②. 取出下一个元素,在已经排序的元素序列中从后向前扫描

③. 如果该元素(已排序)大于新元素,将该元素移到下一位置

④. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

⑤. 将新元素插入到该位置后

⑥. 重复步骤②~⑤
在这里插入图片描述
代码实现
①. 移位法:

public static void sort(int[] a) {
        if (a == null || a.length == 0) {
            return;
        }

        for (int i = 1; i < a.length; i++) {
            int j = i - 1;
            int temp = a[i]; // 先取出待插入数据保存,因为向后移位过程中会把覆盖掉待插入数
            while (j >= 0 && a[j] > temp) { // 如果待是比待插入数据大,就后移
                a[j+1] = a[j];
                j--;
            }
            a[j+1] = temp; // 找到比待插入数据小的位置,将待插入数据插入
        }
    }

而交换法不 需求 额外的保存待插入数据,通过不停的向前交换带插入数据,类似冒泡法,直到找到比它小的值,也就是待插入数据找到了自己的位置。
②. 交换法:

public static void sort2(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }

        for (int i = 1; i < arr.length; i ++) {
            int j = i - 1;
            while (j >= 0 && arr[j] > arr[i]) {
                arr[j + 1] = arr[j] + arr[j+1];      //只要大就交换操作
                arr[j] = arr[j + 1] - arr[j];
                arr[j + 1] = arr[j + 1] - arr[j];
                System.out.println("Sorting:  " + Arrays.toString(arr));
            }
        }
    }

算法效率
直接插入排序不是稳定的排序算法。
在这里插入图片描述

选择排序

基本思想
在未排序序列中找到最小(大)元素,存放到未排序序列的起始位置。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

算法描述
①. 从待排序序列中,找到关键字最大的元素;

②. 如果最大元素不是待排序序列的最后一个元素,将其和最后一个元素互换;

③. 从余下的 N – 1 个元素中,找出关键字最大的元素,重复①、②步,直到排序结束。

代码实现

public class SelectSort {
    public static void sort(int[] arr) {
        int index=0;
        for (int i = 1; i < arr.length ; i++) {
            for (int j = 1; j < arr.length-i; j ++) { //选出之后待排序中值最小的位置
                if (arr[j]  > arr[index]) {
                    index=j;
                }
            }
                arr[index] = arr[arr.length-i] + arr[index];
                arr[arr.length-i] = arr[index] - arr[arr.length-i];
                arr[index] = arr[index] -  arr[arr.length-i];
        }
    }

不稳定排序算法,选择排序的简单和直观名副其实,这也造就了它出了名的慢性子,无论是哪种情况,哪怕原数组已排序完成,它也将花费将近n²/2次遍历来确认一遍。 唯一值得高兴的是,它并不耗费额外的内存空间。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值