一、快速排序(递归版)
1、了解荷兰国旗划分问题
1.小于X的放左边,大于X的放右边(左右) (<= 区)[2,5,1,2,3] 目标 4 当前数 <= 目标,当前数和(<= 区)下一个数交换,(<= 区向右扩),当前数跳下一个 当前数 > 目标,当前数跳下一个 2.小于X的放左边,等于X的放中间,大于X的放右边(左中右) (< 区)[2,5,1,3,4](> 区) 目标 4 当前数 < 目标,当前数和(< 区)下一个数交换,(< 区)向右扩,当前数跳下一个 当前数 = 目标,当前数直接跳下一个 当前数 > 目标,当前数和(> 区)前一个数交换,(> 区)向左扩,当前数停在原地(因为交换来的数还没看过)
快排 1.0 版本:O(N^2),2个区域(小于等于arr[R],大于arr[R]),一次搞定一个数
public void quickSort(int[] arr) { if(arr == null || arr.length < 2) { return; } process(arr, 0, arr.length - 1); } public void process(int[] arr, int L, int R) { // base case if(L >= R) { return; } // 荷兰国旗划分 L..R partition arr[R] [ <=arr[R] arr[R] >arr[R] ] int M = partition(arr, L, R); // 左边递归 process(arr, L, M - 1); // 右边递归 process(arr, M + 1, R); } // arr[L..R]上,以arr[R]位置的数做划分值 public int partition(int[] arr, int L, int R) { if(L > R) { return -1; } if(L == R) { return L; } // 小于等于 X 的下标位置 int lessEqual = L - 1; // 开始比较的下标 int index = L; while(index < R) { // 当前位置值小于等于最后一个目标值时,小于等于区域往右扩,并交换当前位置的值 if(arr[index] <= arr[R]) { // 交换 swap(arr, index, ++lessEqual); } // 继续下一个位置 index++; } // 交换最后一个位置到合适的位置 swap(arr, ++lessEqual, R); return lessEqual; } // 交换元素方法 public void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
选取数组中最后一个位置的数作目标数,左边界一直往右扩
快排 2.0 版本:O(N^2),3个区域(小于arr[R],大于arr[R],大于arr[R]),一次搞定一批与目标相等的数
public void quickSort(int[] arr) { if(arr == null || arr.length < 2) { return; } process(arr, 0, arr.length - 1); } // 递归过程 public void process(int[] arr, int L, int R) { // base case if(L >= R) { return; } // [eqaulArea[0],eqaulArea[1]] int[] equalArea = partition(arr, L, R); // 左边递归 process(arr, L, eqaulArea[0] - 1); // 右边递归 process(arr, eqaulArea[1] +1, R); } // 荷兰国旗划分 public int[] partition(int[] arr, int L, int R) { if(L > R) { return new int[]{-1, -1}; } if(L == R) { return new int[]{L, R}; } // 小于区域边界 int less = L - 1; // 大于区域边界 int more = R; int index = L; while(index < more) { // 当前位置不能和大于区域边界碰上 if(arr[index] == arr[R]) { // 当前数与目标数相等,当前位置直接跳下一个 index++; } else if(arr[index] < arr[R]) { // 当前数小于目标数,当前数与小于区域右边的一个数交换,当前位置跳下一个,小于区域右扩 swap(arr, index++, ++less); } else { // 当前数大于目标数,当前数与大于区域左边的一个数交换,当前位置不变(交换的还没看),大于区域左阔 swap(arr, index, --more); } } // arr[R]目标数交换到合适位置,放到等于区域 swap(arr, more, R); // 返回等于区域的区间范围边界 return new int[]{less + 1, more}; } // 交换 public void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
选取数组最后一个位置的数为目标数,小于区域往右扩,大于区域往左扩
快排 3.0 版本:随机快排O(N*logN), 不再使用最后一个数作为目标,随机在L…R选择一个交换到最后来作为目标
public void quickSort(int[] arr) { if(arr == null || arr.length < 2) { return; } process(arr, 0, arr.length - 1); } // 递归方法 public void process(int[] arr, int L,int R) { // base case if(L >= R) { return; } // 随机选择一个数作为目标数,交换到最后一个位置 swap(arr, L + (int) (Math.random() * (R - L + 1)), R); int[] equalArea = process(arr, L, R); // 左边递归 process(arr, L, equalArea[0] - 1); // 右边递归 process(arr, equalArea[1] + 1); } // 荷兰国旗划分 public int[] partition(int[] arr, int L, int R) { if(L > R) { return new int[]{-1, -1}; } if(L == R) { return new int[]{L, R}; } // 小于区域边界 int less = L - 1; // 大于区域边界 int more = R; int index = L; while(index < more) { // 当前位置不能和大于区域边界碰上 if(arr[index] == arr[R]) { // 当前数与目标数相等,当前位置直接跳下一个 index++; } else if(arr[index] < arr[R]) { // 当前数小于目标数,当前数与小于区域右边的一个数交换,当前位置跳下一个,小于区域右扩 swap(arr, index++, ++less); } else { // 当前数大于目标数,当前数与大于区域左边的一个数交换,当前位置不变(交换的还没看),大于区域左阔 swap(arr, index, --more); } } // arr[R]目标数交换到合适位置,放到等于区域 swap(arr, more, R); // 返回等于区域的区间范围边界 return new int[]{less + 1, more}; } // 交换 public void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }
随机选择一个位置作为目标数
二、快速排序(非递归版)
使用栈来实现
// 快排非递归版本需要的辅助类 // 要处理的是什么范围上的排序 public static class Op { public int l; public int r; public Op(int left, int right) { l = left; r = right; } } // 快排3.0 非递归版本 用栈来执行 public void quickSort(int[] arr) { if (arr == null || arr.length < 2) { return; } int N = arr.length; // 随机选择一个位置的数作为目标,交换到最后 swap(arr, (int) (Math.random() * N), N - 1); // 荷兰国旗划分一次,得到两个区间(左右) int[] equalArea = partition(arr, 0, N - 1); int el = equalArea[0]; int er = equalArea[1]; Stack<Op> stack = new Stack<>(); // 压入得到的左右区间到栈中 stack.push(new Op(0, el - 1)); stack.push(new Op(er + 1, N - 1)); // 栈调用 while (!stack.isEmpty()) { // 弹出一个区间 Op op = stack.pop(); // op.l ... op.r if (op.l < op.r) { // 再次随机选择一个位置的数作为目标,交换到最后位置 swap(arr, op.l + (int) (Math.random() * (op.r - op.l + 1)), op.r); // 荷兰国旗划分,再次得到两个区间(左右) equalArea = partition(arr, op.l, op.r); el = equalArea[0]; er = equalArea[1]; // 再次将得到的左右区间压入到栈中 stack.push(new Op(op.l, el - 1)); stack.push(new Op(er + 1, op.r)); } } } // 荷兰国旗问题 public int[] process(int[] arr, int L, int R) { if (L > R) { return new int[] { -1, -1 }; } if (L == R) { return new int[] { L, R }; } int less = L - 1; int more = R; int index = L; while (index < more) { if (arr[index] == arr[R]) { index++; } else if (arr[index] < arr[R]) { swap(arr, index++, ++less); } else { swap(arr, index, --more); } } swap(arr, more, R); return new int[] { less + 1, more }; }
使用队列来实现
// 快排非递归版本需要的辅助类 // 要处理的是什么范围上的排序 public static class Op { public int l; public int r; public Op(int left, int right) { l = left; r = right; } } // 快排3.0 非递归版本 用队列来执行 public static void quickSort(int[] arr) { if (arr == null || arr.length < 2) { return; } int N = arr.length; swap(arr, (int) (Math.random() * N), N - 1); int[] equalArea = process(arr, 0, N - 1); int el = equalArea[0]; int er = equalArea[1]; Queue<Op> queue = new LinkedList<>(); queue.offer(new Op(0, el - 1)); queue.offer(new Op(er + 1, N - 1)); while (!queue.isEmpty()) { Op op = queue.poll(); if (op.l < op.r) { swap(arr, op.l + (int) (Math.random() * (op.r - op.l + 1)), op.r); equalArea = process(arr, op.l, op.r); el = equalArea[0]; er = equalArea[1]; queue.offer(new Op(op.l, el - 1)); queue.offer(new Op(er + 1, op.r)); } } } // 荷兰国旗问题 public int[] process(int[] arr, int L, int R) { if (L > R) { return new int[] { -1, -1 }; } if (L == R) { return new int[] { L, R }; } int less = L - 1; int more = R; int index = L; while (index < more) { if (arr[index] == arr[R]) { index++; } else if (arr[index] < arr[R]) { swap(arr, index++, ++less); } else { swap(arr, index, --more); } } swap(arr, more, R); return new int[] { less + 1, more }; }
三、快速排序(双向链表版)
较为复杂 - 了解即可
public static class Node { public int value; public Node last; public Node next; public Node(int v) { value = v; } } public static Node quickSort(Node h) { if (h == null) { return null; } int N = 0; Node c = h; Node e = null; while (c != null) { N++; e = c; c = c.next; } return process(h, e, N).h; } public static class HeadTail { public Node h; public Node t; public HeadTail(Node head, Node tail) { h = head; t = tail; } } // L...R是一个双向链表的头和尾, // L的last指针指向null,R的next指针指向null // 也就是说L的左边没有,R的右边也没节点 // 就是一个正常的双向链表,一共有N个节点 // 将这一段用随机快排的方式排好序 // 返回排好序之后的双向链表的头和尾(HeadTail) public static HeadTail process(Node L, Node R, int N) { if (L == null) { return null; } if (L == R) { return new HeadTail(L, R); } // L..R上不只一个节点 // 随机得到一个随机下标 int randomIndex = (int) (Math.random() * N); // 根据随机下标得到随机节点 Node randomNode = L; while (randomIndex-- != 0) { randomNode = randomNode.next; } // 把随机节点从原来的环境里分离出来 // 比如 a(L) -> b -> c -> d(R), 如果randomNode = c,那么调整之后 // a(L) -> b -> d(R), c会被挖出来,randomNode = c if (randomNode == L || randomNode == R) { if (randomNode == L) { L = randomNode.next; L.last = null; } else { randomNode.last.next = null; } } else { // randomNode一定是中间的节点 randomNode.last.next = randomNode.next; randomNode.next.last = randomNode.last; } randomNode.last = null; randomNode.next = null; Info info = partition(L, randomNode); // <randomNode的部分去排序 HeadTail lht = process(info.lh, info.lt, info.ls); // >randomNode的部分去排序 HeadTail rht = process(info.rh, info.rt, info.rs); // 左部分排好序、右部分排好序 // 把它们串在一起 if (lht != null) { lht.t.next = info.eh; info.eh.last = lht.t; } if (rht != null) { info.et.next = rht.h; rht.h.last = info.et; } // 返回排好序之后总的头和总的尾 Node h = lht != null ? lht.h : info.eh; Node t = rht != null ? rht.t : info.et; return new HeadTail(h, t); } public static class Info { public Node lh; public Node lt; public int ls; public Node rh; public Node rt; public int rs; public Node eh; public Node et; public Info(Node lH, Node lT, int lS, Node rH, Node rT, int rS, Node eH, Node eT) { lh = lH; lt = lT; ls = lS; rh = rH; rt = rT; rs = rS; eh = eH; et = eT; } } // (L....一直到空),是一个双向链表 // pivot是一个不在(L....一直到空)的独立节点,它作为划分值 // 根据荷兰国旗问题的划分方式,把(L....一直到空)划分成: // <pivot 、 =pivot 、 >pivot 三个部分,然后把pivot融进=pivot的部分 // 比如 4(L)->6->7->1->5->0->9->null pivot=5(这个5和链表中的5,是不同的节点) // 调整完成后: // 4->1->0 小于的部分 // 5->5 等于的部分 // 6->7->9 大于的部分 // 三个部分是断开的 // 然后返回Info: // 小于部分的头、尾、节点个数 : lh,lt,ls // 大于部分的头、尾、节点个数 : rh,rt,rs // 等于部分的头、尾 : eh,et public static Info partition(Node L, Node pivot) { Node lh = null; Node lt = null; int ls = 0; Node rh = null; Node rt = null; int rs = 0; Node eh = pivot; Node et = pivot; Node tmp = null; while (L != null) { tmp = L.next; L.next = null; L.last = null; if (L.value < pivot.value) { ls++; if (lh == null) { lh = L; lt = L; } else { lt.next = L; L.last = lt; lt = L; } } else if (L.value > pivot.value) { rs++; if (rh == null) { rh = L; rt = L; } else { rt.next = L; L.last = rt; rt = L; } } else { et.next = L; L.last = et; et = L; } L = tmp; } return new Info(lh, lt, ls, rh, rt, rs, eh, et); }