• 排序


    排序

    算法 稳定性 时间复杂度 空间复杂度
    选择排序 N (N^2) 1
    冒泡排序 Y (N^2) 1
    插入排序 Y (N~N^2) 1
    希尔排序 N (N^{(1.3-2)}) 1
    归并排序 Y (NlogN) N
    快速排序 N (NlogN) logN
    堆排序 N (NlogN) 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;
    	}
    }
    

    选择排序

    从数组中 选择最小元素,将它与数组的第一个元素 交换 位置。再从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。

    • (N^2 /2) 次比较
    • (N) 次交换
    • 不稳定
    • 运行时间与输入无关,即对一个已经排序的数组也需要这么多的比较和交换操作
    public class Selection<T extends Comparable<T>> extends Sort<T> {
    
    	@Override
    	public void sort(T[] nums) {
    		int len = nums.length;
    		for (int i = 0; i < len - 1; i++) {
    			int min = i;
    			for (int j = i + 1; j < len; j++) {
    				if (less(nums[j], nums[min])) {
    					min = j;
    				}
    			}
    			swap(nums, i, min);
    		}
    	}
    }
    
    

    冒泡排序

    从左到右不断 交换相邻逆序 的元素,在一轮的循环之后,可以让未排序的最大元素上浮到右侧。
    优化:在一轮循环中,如果没有发生交换,那么说明数组已经是有序的。

    • 最好 (N-1) 次比较 (0) 次交换
    • 最差 (N(N-1)/2) 次比较 (N(N-1)/2) 次交换
    • 稳定
    public class Bubble<T extends Comparable<T>> extends Sort<T> {
    
    	@Override
    	public void sort(T[] nums) {
    		int n = nums.length;
    		boolean isSorted = false;
    		for (int i = n - 1; !isSorted && i > 0; i--) {
    			isSorted = true;
    			for (int j = 0; j < i; j++) {
    				if (less(nums[j + 1], nums[j])) {
    					swap(nums, j, j + 1);
    					isSorted = false;
    				}
    			}
    		}
    	}
    }
    

    插入排序

    每次都将当前元素 插入到左侧已经排序 的数组中,使得左侧数组依然有序。

    插入排序每次只能交换相邻元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。

    • 平均 (N^2/4) 次比较 (N^2/4) 次交换
    • 最坏 (N^2/2) 次比较 (N^2/2) 次交换
    • 最好 (N-1) 次比较 (0) 次交换
    • 稳定
    • 运行时间取决于数组的初始顺序
    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);
    			}
    		}
    	}
    
    	public static void main(String[] args) {
    		Sort.test(new Insertion<Integer>());
    	}
    }
    

    希尔排序

    希尔排序使用 插入排序对间隔 h 的序列 进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序。
    希尔排序通过交换不相邻的元素,每次可以将逆序数量减少大于 1。

    插入排序每次只能交换相邻元素,令逆序数量减少 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;
    		}
    
    		while (h >= 1) {
    			for (int i = h; i < n; i++) {
    				for (int j = i; j >= h && less(nums[j], nums[j - h]); j = j - h) {
    					swap(nums, j, j - h);
    				}
    			}
    			h /= 3;
    		}
    	}
    }
    

    归并排序

    分而治之 ,将问题分解成几个规模较小的问题,再合并子问题获得答案。

    • 稳定
    • 时间复杂度:(Ο(nlogn))
    public class MergeSort<T extends Comparable<T>> extends Sort<T> {
    
    	private T[] tmp;
    
    	@Override
    	public void sort(T[] nums) {
    		int n = nums.length;
    		tmp = Arrays.copyOf(nums, n);
    		sortTopDown(nums, 0, n - 1);
    //		sortDownTop(nums, n);
    	}
    
    	private void sortTopDown(T[] nums, int l, int r) {
    		if (l >= r)
    			return;
    		int m = ((r - l) >> 1) + l;
    		sortTopDown(nums, l, m);
    		sortTopDown(nums, m + 1, r);
    		merge(nums, l, m, r);
    	}
    
    	private void sortDownTop(T[] nums, int n) {
    		for (int d = 1; d < n; d += d) {
    			for (int l = 0; l < n - d; l += d + d) {
    				merge(nums, l, l + d - 1, Math.min(l + d + d - 1, n - 1));
    			}
    		}
    	}
    
    	private void merge(T[] nums, int l, int m, int r) {
    		int i = l;
    		int j = m + 1;
    		int index = l;
    		while (i <= m && j <= r) {
    			if (!less(nums[j], nums[i])) {
    				tmp[index++] = nums[i++];
    			} else {
    				tmp[index++] = nums[j++];
    			}
    		}
    		while (i <= m) {
    			tmp[index++] = nums[i++];
    		}
    		while (j <= r) {
    			tmp[index++] = nums[j++];
    		}
    		for (index = l; index <= r; index++) {
    			nums[index] = tmp[index];
    		}
    	}
    }
    

    快速排序

    快速排序通过一个 切分元素 将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。

    public class QuickSort<T extends Comparable<T>> extends Sort<T> {
    
    	@Override
    	public void sort(T[] nums) {
    		sort(nums, 0, nums.length - 1);
    	}
    
    	private void sort(T[] nums, int l, int r) {
    		if (l >= r) {
    			return;
    		}
    		int index = partition(nums, l, r);
    		sort(nums, l, index - 1);
    		sort(nums, index + 1, r);
    	}
    
    	private int partition(T[] nums, int l, int r) {
    		T tmp = nums[r];
    		int i = l;
    		int j = r - 1;
    		while (true) {
    			// 相等时即停止移动指针
    			while (i < r && less(nums[i], tmp))
    				i++;
    			while (j >= l && less(tmp, nums[j]))
    				j--;
    			if (i >= j)
    				break;
    			swap(nums, i++, j--);
    		}
    		swap(nums, i, r);
    		return i;
    	}
    }
    

    :若指针指向的元素与参考值相等仍继续移动,假设极端情况:数组中所有元素都相等。那么i会移动到最后一个位置,导致数组切分失衡,复杂度变为 (O(n^2))。因此相等时就应停止移动,虽然多了许多交换操作,但能够保证切分均衡。

    改进 :三向切分。lt左侧小于切分元素,gt右侧大于切分元素。

    private void threeWayQuickSort(T[] nums, int l, int r) {
    	if (l >= r) {
    		return;
    	}
    	int lt = l, i = l + 1, gt = r;
    	T tmp = nums[l];
    	while (i <= gt) {
    		int cmp = nums[i].compareTo(tmp);
    		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, r);
    }
    

    堆排序

    堆是 完全二叉树 ,可以用数组来表示。
    位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。(不使用数组索引为 0 的位置)

    • 堆的高度为 (logN),因此在堆中插入元素和删除最大元素的复杂度都为 (logN)
    • 对于堆排序,由于要对 N 个节点进行下沉操作,因此复杂度为 (NlogN)
    • 不稳定
    • 正常来说,堆排序是 原地排序 ,这里为了实现简单,下标从1开始,使用了辅助数组。
    public class HeapSort<T extends Comparable<T>> extends Sort<T> {
    
    	@Override
    	public void sort(T[] nums) {
    		int n = nums.length;
    		T[] tmp = (T[]) new Comparable[n + 1];
    		System.arraycopy(nums, 0, tmp, 1, n);
    
    		for (int i = n / 2; i >= 1; i--) {
    			sink(tmp, i, n);
    		}
    		for (int i = 1; i <= n; i++) {
    			swap(tmp, 1, n - i + 1);
    			sink(tmp, 1, n - i);
    		}
    
    		System.arraycopy(tmp, 1, nums, 0, n);
    	}
    
    	private void sink(T[] nums, int k, int n) {
    		while (k <= n / 2) {
    			int t = 2 * k;
    			if (t + 1 <= n && less(nums[t], nums[t + 1])) {
    				t++;
    			}
    			if (!less(nums[k], nums[t]))
    				break;
    			swap(nums, k, t);
    			k = t;
    		}
    	}
    }
    
  • 相关阅读:
    字典的功能 (用在判断中的功能)
    PAT (Advanced Level) Practice 1144 The Missing Number (20分)
    LCA算法实际应用 PAT (Advanced Level) Practice 1151 LCA in a Binary Tree (30分) 四种方法完成题目+tarjan详细讲解!
    记笔记最好用的超高颜值软件之一!Typora 你值得拥有!
    PAT (Advanced Level) Practice 1150 Travelling Salesman Problem (25分) (正常思路)
    PAT (Advanced Level) Practice 1154 Vertex Coloring (25分) (直接存边遍历)
    PAT (Basic Level) Practice (中文) 1095 解码PAT准考证 (25分) (unordered_map的使用 多做几遍!!)
    PAT (Basic Level) Practice (中文) 1093 字符串A+B (20分)
    PAT (Basic Level) Practice (中文)1092 最好吃的月饼 (20分)
    51Nod 1192 Gcd表中的质数
  • 原文地址:https://www.cnblogs.com/JL916/p/12540045.html
Copyright © 2020-2023  润新知