二分搜索
对于一个有序数组,采用二分搜索的方式,可将时间复杂度由O(n) 降为 O(logn)。
二分搜索的思想即,每次取数组中间位置的值与目标进行比较,根据比较结果可排除当前搜索范围的一半。
二分搜索的难点在于边界的判断,规定搜索区间即可解决这一问题,常选择的区间为左闭右开。
public int searchInsert(int[] nums, int target) {
// 指针初始化,这里体现左闭右开[0, n)
int l = 0; // 左指针
int r = nums.length; // 右指针
while (l < r){
int mid = l + (r - l) / 2; // 若采用(r + l) / 2 会有超出int最大值的风险
if (nums[mid] > target){
r = mid; // 右指针等于mid。(体现右开原则)
}else if (nums[mid] < target){
l = mid + 1; // 左指针等于mid + 1。 (体现左闭原则)
}else {
return mid;
}
}
return l;
}
快排
快排相较于选择排序和冒泡排序,可将时间复杂度由O(n2) 降为O(nlogn)。
快排的思想为:以左边为基准,定义左右指针(左闭右闭),左指针向右移动直至遇到大于基准值,右指针向左移动直至小于基准值。随后两者交换,直至两个指针相等。然后与基准值交换,这样就能保证交换后的基准值,左边都小于基准值,右边都大于基准值。最后利用递归,将左半部分和右半部分进行上述操作。
需要注意的是,左指针与基准相等,以左边为基准,左边的右边先向左移。
public void quickSort(int[] nums, int l, int r) {
if (l >= r) {
return;
}
int i = l;
int j = r;
while (j > i) {
// 找到比第一位小的值, 所以这里符号为 >=
while (nums[j] >= nums[l] && j > i) { j--; };
// 找到比第一位大的值, 所以这里符号为 <=
while (nums[i] <= nums[l] && j > i) { i++; };
// 两者交换
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
// 第一位进行交换
int temp = nums[l];
nums[l] = nums[i];
nums[i] = temp;
// 前半段递归
quickSort(nums, l, i - 1);
// 后半段递归
quickSort(nums, i + 1, r);
}
堆排序
堆的概念是由二叉树引出了的,该二叉树的特点为根节点大于子节点(大顶堆)。
大顶堆每次可取出最大值,随后通过取两个子节点进行下沉操作,以维持大顶堆的结构。
添加元素时,将添加元素与根元素进行比较,进行上浮操作,以维持大顶堆的结构。
时间复杂度也为O(nlogn),为不稳定排序,极端情况下退化为O(n2),java中Arrays.sort(),采用的排序方式即为堆排序。
public class MaxHeap<E extends Comparable<E>> {
public static void main(String[] args) {
MaxHeap<Integer> maxHeap = new MaxHeap<>();
Random rand = new Random();
for (int i = 0; i < 10; i++) {
maxHeap.add(rand.nextInt(10));
}
while (!maxHeap.isEmpty()) {
System.out.println(maxHeap.poll());
}
}
private List<E> list;
// 无参构造
public MaxHeap() {
list = new ArrayList<>();
}
// 有参构造
public MaxHeap(int capacity) {
list = new ArrayList<>(capacity);
}
// 返回堆的容量
public int getSize() {
return list.size();
}
// 判断堆是否为空
public boolean isEmpty() {
return list.isEmpty();
}
// 获取左子节点元素
public int getLeft(int index) {
return index * 2 + 1;
}
// 获取右子节点元素
public int getRight(int index) {
return index * 2 + 2;
}
// 获取父节点元素
public int getParent(int index) {
if (index == 0) {
throw new IllegalArgumentException("0元素不包含父亲节点!");
}
return (index - 1) / 2;
}
// 添加元素
public void add(E e) {
list.add(e);
siftUp(list.size() - 1);
}
// 上浮操作
public void siftUp(int index) {
if (index == 0) {
return;
}
int parentIndex = getParent(index);
E e = list.get(index);
E parent = list.get(parentIndex);
if (e.compareTo(parent) > 0) {
swap(index, parentIndex);
siftUp(parentIndex);
}
}
// 获取最大值
public E getMax() {
if (list.isEmpty()) {
throw new IllegalArgumentException("堆为空!");
}
return list.get(0);
}
// 取出最大值
public E poll() {
E ret = getMax();
// 将最大值与末尾交换
swap(0, list.size() - 1);
// 删除最大值
list.remove(list.size() - 1);
// 下沉操作
siftdown(0);
return ret;
}
// 下沉操作
public void siftdown(int index) {
if (getLeft(index) >= list.size()) {
// 如果是叶子节点,不用下沉操作。
return;
}
// list.get(maxIndex) 是左右节点的最大值。
int maxIndex = getLeft(index);
if (maxIndex + 1 < list.size() && list.get(maxIndex).compareTo(list.get(maxIndex + 1)) < 0) {
maxIndex++;
}
if (list.get(index).compareTo(list.get(maxIndex)) < 0) {
// 与子节点交换
swap(index, maxIndex);
siftdown(maxIndex);
}
}
// 交换操作
public void swap(int i, int j) {
E temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
}
归并排序
归并排序的思想是分治,我们可以把长度为n的数组,平分成两个长度为n / 2的数组,并将平分之后的数组分别排序,最后合并这两个大小为n /2 的有序数组。
归并排序的时间复杂度为O(nlogn),为稳定排序。
public class MergeSort {
public static void main(String[] args) {
int[] nums = {1,4,3,7,8,2,0,9,5,6};
MergeSort mergeSort = new MergeSort();
mergeSort.sort(nums, 0, nums.length - 1);
System.out.println(Arrays.toString(nums));
}
public void sort(int[] nums, int head, int tail) {
if (head < tail) {
int mid = head + (tail - head) / 2;
sort(nums, head, mid);
sort(nums, mid + 1, tail);
merge(nums, head, mid, tail);
}
}
public void merge(int[] nums, int head, int mid, int tail) {
int p = head;
int q = mid + 1;
// 申请临时空间存放
int[] temp = new int[tail - head + 1];
int k = 0;
while (p <= mid && q <= tail) {
if (nums[p] > nums[q]) {
temp[k++] = nums[p++];
}else {
temp[k++] = nums[q++];
}
}
if (p > mid) {
while (q <= tail) {
temp[k++] = nums[q++];
}
}else {
while (p <= mid) {
temp[k++] = nums[p++];
}
}
k--;
for (int i = tail; i >= head; i--) {
nums[i] = temp[k--];
}
}
}