排序(算法复习一)

选择排序

  • 时间复杂度: N 2 N^2 N2
  • 空间复杂度:1
  • 实现逻辑:把最小的数据依次往前放,第一遍找出第一小的数据,然后和第一个位置的数据互换,第二遍找出第二小的数据,和第二个位置的数据互换
  • 代码:
public abstract class Sort<T extends Comparable<T>> {

    public abstract void sort(T[] nums);

    protected boolean less(T v, T w) {
        return v.compareTo(w) < 0;
    }

    protected void swap(T[] a, int i, int j) {
        T t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
}
public class Selection<T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        int N = nums.length;
        for (int i = 0; i < N - 1; i++) {
            int min = i;
            for (int j = i + 1; j < N; j++) {
                if (less(nums[j], nums[min])) {
                    min = j;
                }
            }
            swap(nums, i, min);
        }
    }
}

冒泡排序

  • 时间复杂度: N 2 N^2 N2
  • 空间复杂度:1
  • 实现逻辑:从左向右两两比较相邻的值,如果左边大于右边,则进行交换,第一遍交换完成后,最大的值在最右侧,第二遍遍历截止的元素则为倒数第二个元素,结束的标志为:1.遍历截止的值为第一个时,可停止;2.一遍便利后,没有值交换,则已经排序完成,可退出
  • 代码:
public class Bubble<T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        int N = nums.length;
        boolean hasSorted = false;
        for (int i = N - 1; i > 0 && !hasSorted; i--) {
            hasSorted = true;
            for (int j = 0; j < i; j++) {
                if (less(nums[j + 1], nums[j])) {
                    hasSorted = false;
                    swap(nums, j, j + 1);
                }
            }
        }
    }
}

插入排序

  • 时间复杂度: N − N 2 N-N^2 NN2
  • 空间复杂度:1
  • 实现逻辑:从左向右扩大比较数组,第一遍为两个元素的数组,左边大于右边则进行交换,第二遍为三个元素的数组,先比较第二个和第三个元素,需要交换则交换,并且比较第一个和第二个元素,若不需要交换,则进入第三遍4个元素,直至遍历到最后一个元素
  • 代码:
public class Insertion<T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        int N = nums.length;
        for (int i = 1; i < N; i++) {
            for (int j = i; j > 0 && less(nums[j], nums[j - 1]); j--) {
                swap(nums, j, j - 1);
            }
        }
    }
}

希尔排序

  • 时间复杂度: N 3 / 2 N^{3/2} N3/2
  • 空间复杂度:1
  • 实现逻辑:是插入排序的升级版,插入排序每次都比较相邻的元素,故每次逆序数量只能减少1,而希尔排序第一遍比较为数组长度/3后的值作为间隔,比较此间隔的数组,并按照插入排序进行比较,第二遍为第一遍间隔/3的值作为间隔,再进行插入排序,直至间隔为1插入排序完成后,则结束
  • 代码:
public class Shell<T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {

        int N = nums.length;
        int h = 1;

        while (h < N / 3) {
            h = 3 * h + 1; // 1, 4, 13, 40, ...
        }

        while (h >= 1) {
            for (int i = h; i < N; i++) {
                for (int j = i; j >= h && less(nums[j], nums[j - h]); j -= h) {
                    swap(nums, j, j - h);
                }
            }
            h = h / 3;
        }
    }
}

归并排序

  • 时间复杂度:NlogN
  • 空间复杂度:N
  • 实现逻辑:将数组通过递归的方式不断将数组拆分成两个数组,直到数组只有一个元素就直接返回,拆分成的两个数组已经通过递归排序完成,再调用合并方法,合并方法通过一个备份的数组,依次比较前半段和后半段的数组,第一遍比较前半段第一个元素和后半段第一个元素,若前半段第一个元素小,则前半段第一个元素复制给原有数组的最左侧,并前半段的位置向右移动一位,后半段第一个元素小亦然,第二遍比较前半段第二个元素和后半段第一个元素,直至所有元素赋值到原有的数组
  • 自顶向下归并排序:通过将数组不断二分递归的方式进行
  • 自底向上归并排序:通过循环的方式,先对一个元素的两个数组排序,在对两个元素的两个数组排序,直至待排序的数组元素个数和初始数组长度相同
  • 代码:
public abstract class MergeSort<T extends Comparable<T>> extends Sort<T> {

    protected T[] aux;


    protected void merge(T[] nums, int l, int m, int h) {

        int i = l, j = m + 1;

        for (int k = l; k <= h; k++) {
            aux[k] = nums[k]; // 将数据复制到辅助数组
        }

        for (int k = l; k <= h; k++) {
            if (i > m) {
                nums[k] = aux[j++];

            } else if (j > h) {
                nums[k] = aux[i++];

            } else if (aux[i].compareTo(aux[j]) <= 0) {
                nums[k] = aux[i++]; // 先进行这一步,保证稳定性

            } else {
                nums[k] = aux[j++];
            }
        }
    }
}
// 自上而下
public class Up2DownMergeSort<T extends Comparable<T>> extends MergeSort<T> {

    @Override
    public void sort(T[] nums) {
        aux = (T[]) new Comparable[nums.length];
        sort(nums, 0, nums.length - 1);
    }

    private void sort(T[] nums, int l, int h) {
        if (h <= l) {
            return;
        }
        int mid = l + (h - l) / 2;
        sort(nums, l, mid);
        sort(nums, mid + 1, h);
        merge(nums, l, mid, h);
    }
}
// 自下而上
public class Down2UpMergeSort<T extends Comparable<T>> extends MergeSort<T> {

    @Override
    public void sort(T[] nums) {

        int N = nums.length;
        aux = (T[]) new Comparable[N];

        for (int sz = 1; sz < N; sz += sz) {
            for (int lo = 0; lo < N - sz; lo += sz + sz) {
                merge(nums, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));
            }
        }
    }
}

快速排序

  • 时间复杂度:NlogN
  • 空间复杂度logN:
  • 实现逻辑:
    1. 先对数组进行切分,切分的过程是取数组最左侧的值,作为切分元素,从左向右选出第一个比它大的元素,再从右向左选出第一个比起小的元素,进行交换,直至两个指针相遇后,切分元素和从右边开始的指针进行交换,返回右边指针的位置
    2. 再对切分的数组进行左半部分和右半部分的递归调用
  • 代码:
public class QuickSort<T extends Comparable<T>> extends Sort<T> {

    @Override
    public void sort(T[] nums) {
        shuffle(nums);
        sort(nums, 0, nums.length - 1);
    }

    private void sort(T[] nums, int l, int h) {
        if (h <= l)
            return;
        int j = partition(nums, l, h);
        sort(nums, l, j - 1);
        sort(nums, j + 1, h);
    }

    private void shuffle(T[] nums) {
        List<Comparable> list = Arrays.asList(nums);
        Collections.shuffle(list);
        list.toArray(nums);
    }
    
	private int partition(T[] nums, int l, int h) {
	    int i = l, j = h + 1;
	    T v = nums[l];
	    while (true) {
	        while (less(nums[++i], v) && i != h) ;
	        while (less(v, nums[--j]) && j != l) ;
	        if (i >= j)
	            break;
	        swap(nums, i, j);
	    }
	    swap(nums, l, j);
	    return j;
	}
}

三向切分快速排序

  • 时间复杂度:N~NlogN
  • 空间复杂度:logN
  • 实现逻辑:
    1. 先对数组进行切分,切分的方式有所不同,第一个指针指向起始元素,第二个指针从第二个元素开始向右移动,第一个指针和第二指针进行比较,若第一个大于第二个,则两则进行交换,两个指针均向右移动一位,若第二个大于第一个,则第二个指针和后边的第三个指针进行交换,后边的指针想左移动一位,直至第二个指针和后边的第三个指针相遇以后,结束
    2. 再对切分的数组进行左半部分和右半部分的递归调用
  • 代码:
public class ThreeWayQuickSort<T extends Comparable<T>> extends QuickSort<T> {

    @Override
    protected void sort(T[] nums, int l, int h) {
        if (h <= l) {
            return;
        }
        int lt = l, i = l + 1, gt = h;
        T v = nums[l];
        while (i <= gt) {
            int cmp = nums[i].compareTo(v);
            if (cmp < 0) {
                swap(nums, lt++, i++);
            } else if (cmp > 0) {
                swap(nums, i, gt--);
            } else {
                i++;
            }
        }
        sort(nums, l, lt - 1);
        sort(nums, gt + 1, h);
    }
}

堆排序

  • 时间复杂度:NlogN

  • 空间复杂度:1

  • 实现逻辑:

    1. 堆排序是将数组看成一个堆,而堆是一个完全二叉树,而二叉树可以很好地存储在数组中,位置k的父节点是k/2,其两个子节点是2k和2k+1,数组索引为0的位置不使用
    2. 第一步通过下沉的方式,将最大值调整到每个堆的堆顶,也就是父节点不小于其子节点,形成最大堆,该方式只需从叶子节点的上一级n/2向上遍历即可
    3. 第二步替换顶级元素和最尾部的元素,并且堆长度减1
    4. 第三步通过从顶部下沉的方式,将第二大的值放到堆顶,再往复进行第二步和第三步,直至堆长度为1

    ps: 其中下沉的方式为,比较子元素中较大的值,然后父节点再和较大的值比较,若父节点更小,则与子节点较大值替换
    堆排序详解

  • 代码:

public class HeapSort<T extends Comparable<T>> extends Sort<T> {
    /**
     * 数组第 0 个位置不能有元素
     */
    @Override
    public void sort(T[] nums) {
        int N = nums.length - 1;
        for (int k = N / 2; k >= 1; k--)
            sink(nums, k, N);

        while (N > 1) {
            swap(nums, 1, N--);
            sink(nums, 1, N);
        }
    }

    private void sink(T[] nums, int k, int N) {
        while (2 * k <= N) {
            int j = 2 * k;
            if (j < N && less(nums, j, j + 1))
                j++;
            if (!less(nums, k, j))
                break;
            swap(nums, k, j);
            k = j;
        }
    }

    private boolean less(T[] nums, int i, int j) {
        return nums[i].compareTo(nums[j]) < 0;
    }
}

Reference

算法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值