数据结构与算法(java版)第二季 - 1 冒泡、选择、堆排序

初识排序

什么叫排序?

排序前:3,1,6,9,2,5,8,4,7
排序后:1,2,3,4,5,6,7,8,9(升序) 或者 9,8,7,6,5,4,3,2,1(降序)
排序的应用

十大排序算法 

 冒泡排序(Bubble Sort)

冒泡排序也叫做起泡排序
◼ 以升序为例子
① 从头开始比较每一对相邻元素,如果第1个比第2个大,就交换它们的位置
执行完一轮后,最末尾那个元素就是最大的元素
② 忽略 ① 中曾经找到的最大元素,重复执行步骤 ①,直到全部元素有序
代码
public class Bubble_Sort {
    public static void main(String[] args) {

        int[] array = {10,9,20,36,89,45,65,15,64,47};

        //进行一个冒泡排序的过程
        for(int end = array.length-1 ; end > 0;end--) {
            for (int begin = 1; begin <= end; begin++) {
                if (array[begin] < array[begin - 1]) {
                    int tmp = array[begin];
                    array[begin] = array[begin - 1];
                    array[begin - 1] = tmp;
                }
            }
        }

        //进行一个遍历的操作
        for(int i = 0;i < array.length;i++)
        {
            System.out.println(" " + array[i]);
        }

    }
}

冒泡排序 - 优化一

如果给定的数据就是有序的,可以提前终止排序.

 static void bubbleSort2(Integer[] array)
    {
        //生成一个随机数字
        //Integer[] array = Integers.random(10,1,100);
        // 生成一个随机数字,由于jdk的版本,这个版本的java没有这个东西
        //打印可以使用Integers.println(array)
        /*
        
        Times.test("冒泡排序",null);
        */
        //进行一个冒泡排序的过程
        for(int end = array.length-1 ; end > 0;end--) {

            //标记是否有序,放在这个位置的原因是在扫描的过程可能就是有序的
            boolean sorted = true;

            for (int begin = 1; begin <= end; begin++) {

                //一旦这个条件是有序的,说明这个就是无序的
                if (array[begin] < array[begin - 1]) {
                    int tmp = array[begin];
                    array[begin] = array[begin - 1];
                    array[begin - 1] = tmp;

                    sorted = false;//如果要是能进来说明无序
                }
            }

            //只要是检测到排序的过程中,已经排好了,直接跳出即可.
            if(sorted) break;

        }

        //进行一个遍历的操作
        for(int i = 0;i < array.length;i++)
        {
            System.out.println(array[i]);
        }
    }

上述代码用到了一个测试时间的java类,Times.class代码是如下所示:

import java.text.SimpleDateFormat;
import java.util.Date;

public class Times {
    private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS");

    public Times() {
    }

    public static void test(String title, Times.Task task) {
        if (task != null) {
            title = title == null ? "" : "【" + title + "】";
            System.out.println(title);
            System.out.println("开始:" + fmt.format(new Date()));
            long begin = System.currentTimeMillis();
            task.execute();
            long end = System.currentTimeMillis();
            System.out.println("结束:" + fmt.format(new Date()));
            double delta = (double)(end - begin) / 1000.0D;
            System.out.println("耗时:" + delta + "秒");
            System.out.println("-------------------------------------");
        }
    }

    public interface Task {
        void execute();
    }
}

注意:上述的优化过程,只有在数据是提前有序的时候才会花费的时间更少,否则的话,运行的时间还是非常长的.

冒泡排序 - 优化二

如果序列尾部已经局部有序,可以记录最后1次交换的位置,减少比较次数.
由上图观察可知,最后一个需要进行交换的6位置的数据,如果我们是提前可以观察到相应的示意图,就是可以不用进行理会后面的数据的.
 static void bubbleSort3(Integer[] array)
    {

        //进行一个冒泡排序的过程
        for(int end = array.length-1 ; end > 0;end--) {

            //用来记录哪一个是已经替换好的,这里的初始值是存在一些讲究的
            //sortedIndex的初始值在数组完全有序的时候有用
            int sortedIndex = 0;
            
            for (int begin = 1; begin <= end; begin++) {

                //一旦这个条件是有序的,说明这个就是无序的
                if (array[begin] < array[begin - 1]) {
                    int tmp = array[begin];
                    array[begin] = array[begin - 1];
                    array[begin - 1] = tmp;
                    //直接记录最后一次扫描的位置
                    sortedIndex = begin;
                }
                end = sortedIndex;
            }

        }

        //进行一个遍历的操作
        for(int i = 0;i < array.length;i++)
        {
            System.out.println(array[i]);
        }
    }
最坏、平均时间复杂度: O(n^ 2 )
最好时间复杂度: O(n)
空间复杂度: O(1) [没有用到额外的空间]

排序算法的稳定性(Stability)

如果相等的2个元素,在排序前后的相对位置保持不变,那么这是 稳定 的排序算法
排序前: 5, 1, 3 𝑎 , 4, 7, 3 𝑏
稳定的排序: 1, 3 𝑎 , 3 𝑏 , 4, 5, 7
不稳定的排序: 1, 3 𝑏 , 3 𝑎 , 4, 5, 7
◼  对自定义对象进行排序时,稳定性会影响最终的排序效果
冒泡排序属于稳定的排序算法(只有当右边的小于左边的才会进行一个交换)
稍有不慎,稳定的排序算法也能被写成不稳定的排序算法,比如下面的冒泡排序代码是不稳定的

原地算法(In-place Algorithm)

何为原地算法?
不依赖额外的资源或者依赖少数的额外资源,仅依靠输出来覆盖输入
空间复杂度为 𝑂(1) 的都可以认为是原地算法
非原地算法,称为 Not-in-place 或者 Out-of-place
冒泡排序属于 In-place

选择排序(Selection Sort)

执行流程
① 从序列中找出最大的那个元素,然后与最末尾的元素交换位置
执行完一轮后,最末尾的那个元素就是最大的元素
② 忽略 ① 中曾经找到的最大元素,重复执行步骤 ①
选择排序的交换次数要远远少于冒泡排序,平均性能优于冒泡排序
最好、最坏、平均时间复杂度: O(n^ 2 ) ,空间复杂度: O(1) ,属于稳定排序
Asserts.test(判断语句);//可以得到判断语句是否是正确的

选择排序还是有优化的空间,使用堆的方式.

选择排序的优化 - 堆数据结构

堆排序是选择排序的一种优化
执行流程
① 对序列进行原地建堆(heapify)
② 重复执行以下操作,直到堆的元素数量为 1
交换堆顶元素与尾元素
堆的元素数量减 1
对 0 位置进行 1 次 siftDown 操作
  • 对下面的元素进行原地建堆

  • 将堆顶元素拿出来,放到最后面,并且进行一个下滤操作,操作如下所示

  •  重复上一步操作,直到元素0位置是最小的.
  •  最好、最坏、平均时间复杂度:O(nlogn),空间复杂度:O(1),属于不稳定排序

原始的比较代码的写法

  protected void sort() {
        for(int end = array.length -1;end > 0;end--)
        {
            int maxIndex = array[0];
            for(int begin = 1;begin <= end;begin++)
            {
                if(array[maxIndex] <= array[begin]) //<=的原因是要使用稳定的排序算法
                {
                    maxIndex = begin;
                }
            }
            int temp = array[maxIndex];
            array[maxIndex] = array[end];
            array[end] = temp;
        }
    }

但是上述的代码在使用的过程中,交换和比较函数是可以进行提出来的,因此,可以直接进行一个类的外部声明,然后进行调用即可.

父类Sort的写出

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

import java.util.Arrays;

public abstract class Sort implements Comparable<Sort>{
    protected Integer[] array;
    protected int cmpCount;//进行一个比较的次数
    protected int swapCount;//进行交换的此时
    private long time;//记录执行时间

    public void sort(Integer[] array)
    {
        if(array == null || array.length < 2) return;

        this.array = array;
        long begin = System.currentTimeMillis();
        sort();
        time = System.currentTimeMillis() - begin;
    }

    protected abstract void sort();

    private String numberString(int number)
    {
        if(number < 10000) return number + " ";//注意这里是返回字符串,因此,必须要加一个" "

        if(number < 100000000) return number/10000.0 + "万";

        return number/100000000.0 + "亿";

    }
    /*
    返回值等于0,代表array[i1] == array[i2]
    返回值小于0,代表array[i1] < array[i2]
    返回值大于0,代表array[i1] > array[i2]
     */
    protected int cmp(int i1,int i2)
    {
        cmpCount++;
        return array[i1] - array[i2];
    }

    protected int cmpElements(Integer v1,Integer v2)
    {
        cmpCount++;
        return v1 - v2;
    }

    protected void swap(int i1,int i2)
    {
        swapCount++;
        int tmp = array[i1];
        array[i1] = array[i2];
        array[i2] = tmp;

    }

    @Override
    public String toString() {
        return "Sort{" +
                "array=" + Arrays.toString(array) +
                ", cmpCount=" + cmpCount +
                ", swapCount=" + swapCount +
                ",Consume Time =" + time/1000.000 +
                '}';
    }

    public int compareTo(Sort o) {
        int result = (int)(time - o.time);
        if(result != 0) return result;

        result = cmpCount - o.cmpCount;
        if(result != 0) return result;

        return swapCount - o.swapCount;
    }
}

子类SelectionSort代码是如下所示:

public class SelectionSort extends Sort{

    protected void sort() {
        for(int end = array.length -1;end > 0;end--)
        {
            int maxIndex = array[0];
            for(int begin = 1;begin <= end;begin++)
            {
//                if(array[maxIndex] <= array[begin]) //<=的原因是要使用稳定的排序算法
                if(cmp(maxIndex,begin) <= 0)
                {
                    maxIndex = begin;
                }
            }
//            int temp = array[maxIndex];
//            array[maxIndex] = array[end];
//            array[end] = temp;
            swap(maxIndex,end);
        }
    }

}

前面的BubbuleSort也是可以进行一个改正的过程,比较好理解,懒得改了.

堆排序HeapSort

public class HeapSort extends Sort{
    //1.记录堆里面的数据
    private int heapSize;

    protected void sort() {
        //2.原地建堆 - 将array之中的元素变成一个堆
        heapSize = array.length;
        for(int i = (heapSize >>1) -1;i >= 0;i--)
        {
            siftDown(i);
        }
        //3.交换堆顶的元素与尾部的元素
        while(heapSize > 1)
        {
            swap(0,heapSize-1);
            //4.将堆之中的数据减少
            heapSize--;

            //5.对0位置进行siftDown(恢复堆的性质)
            siftDown(0);
        }

    }

    /**
     * 让index位置的元素下滤
     * @param index
     */
    private void siftDown(int index) {
        Integer element = array[index];
        int half = heapSize >> 1;
        // 第一个叶子节点的索引 == 非叶子节点的数量
        // index < 第一个叶子节点的索引
        // 必须保证index位置是非叶子节点
        while (index < half) {
            // index的节点有2种情况
            // 1.只有左子节点
            // 2.同时有左右子节点

            // 默认为左子节点跟它进行比较
            int childIndex = (index << 1) + 1;
            Integer child = array[childIndex];

            // 右子节点
            int rightIndex = childIndex + 1;

            // 选出左右子节点最大的那个
            if (rightIndex < heapSize && cmpElements(array[rightIndex], child) > 0) {
                child = array[childIndex = rightIndex];
            }

            if (cmpElements(element, child) >= 0) break;

            // 将子节点存放到index位置
            array[index] = child;
            // 重新设置index
            index = childIndex;
        }
        array[index] = element;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值