基本排序
归并排序
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
package sortdemo;
import java.util.Arrays;
/**
* Created by chengxiao on 2016/12/8.
*/
public class MergeSort {
public static void main(String []args){
int []arr = {9,8,7,6,5,4,3,2,1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int []arr){
int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
sort(arr,0,arr.length-1,temp);
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
}
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;//左序列指针
int j = mid+1;//右序列指针
int t = 0;//临时数组指针
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){//将左边剩余元素填充进temp中
temp[t++] = arr[i++];
}
while(j<=right){//将右序列剩余元素填充进temp中
temp[t++] = arr[j++];
}
t = 0;
//将temp中的元素全部拷贝到原数组中
while(left <= right){
arr[left++] = temp[t++];
}
}
}
链表排序
要求: O(n log n) 时间复杂度和常数级空间复杂度下
我的做法:
堆排:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
if(Objects.isNull(head) || Objects.isNull(head.next)){
return head;
}
PriorityQueue<ListNode> queue = new PriorityQueue<>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val-o2.val;
}
});
ListNode node = head;
while (Objects.nonNull(node)){
queue.add(node);
node = node.next;
}
ListNode node1 = queue.poll();
ListNode result = node1;
while (!queue.isEmpty()){
node1.next = queue.poll();
node1 = node1.next;
}
node1.next = null;
return result;
}
}
归并
class Solution {
public ListNode sortList(ListNode head) {
if(head == null || head.next == null) return head;
// 第一步:将链表拆成两半
ListNode fast = head, slow = head, prev = head;
while(fast != null && fast.next != null) {
prev = slow;
slow = slow.next;
fast = fast.next.next;
}
prev.next = null;
// 第二步:将两部分的链表分别排序
ListNode l1 = sortList(head);
ListNode l2 = sortList(slow);
// 第三步:合并两个有序链表
return merge(l1,l2);
}
private ListNode merge(ListNode l1, ListNode l2) {
ListNode p = new ListNode(), l = p;
while(l1 != null && l2 != null) {
if(l1.val < l2.val) {
p.next = l1;
l1 = l1.next;
} else {
p.next = l2;
l2 = l2.next;
}
p = p.next;
}
if(l1 != null) {
p.next = l1;
}
if(l2 != null) {
p.next = l2;
}
return l.next;
}
}
复杂度分析
时间复杂度:O(n)O(n),如上文所述,证明过程可以参考「《算法导论》9.2:期望为线性的选择算法」。
空间复杂度:O(\log n)O(logn),递归使用栈空间的空间代价的期望为 O(\log n)O(logn)。
class Solution {
Random random = new Random();
public int findKthLargest(int[] nums, int k) {
return quickSelect(nums, 0, nums.length - 1, nums.length - k);
}
public int quickSelect(int[] a, int l, int r, int index) {
int q = randomPartition(a, l, r);
if (q == index) {
return a[q];
} else {
return q < index ? quickSelect(a, q + 1, r, index) : quickSelect(a, l, q - 1, index);
}
}
public int randomPartition(int[] a, int l, int r) {
int i = random.nextInt(r - l + 1) + l;
swap(a, i, r);
return partition(a, l, r);
}
public int partition(int[] a, int l, int r) {
int x = a[r], i = l - 1;
for (int j = l; j < r; ++j) {
if (a[j] <= x) {
swap(a, ++i, j);
}
}
swap(a, i + 1, r);
return i + 1;
}
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
class Solution {
Random rand=new Random();
public int findKthLargest(int[] nums, int k) {
return quickSort(nums,k,0,nums.length-1);
}
private int quickSort(int[] nums,int k,int left,int right){
int index=rand.nextInt(right-left+1)+left;
int flag=nums[index];
nums[index]=nums[left];
int i=left,j=right;
while (i<j){
while (i<j&&nums[j]<=flag) j--;
nums[i]=nums[j];
while (i<j&&nums[i]>=flag) i++;
nums[j]=nums[i];
}
nums[i]=flag;
if (i==k-1) return nums[i];
else if (i<k-1) return quickSort(nums,k,i+1,right);
else return quickSort(nums,k,left,i-1);
}
}
方法二:基于堆排序的选择方法
复杂度分析
时间复杂度:O(n \log n)O(nlogn),建堆的时间代价是 O(n)O(n),删除的总代价是 O(k \log n)O(klogn),因为 k < nk<n,故渐进时间复杂为 O(n + k \log n) = O(n \log n)O(n+klogn)=O(nlogn)。
空间复杂度:O(\log n)O(logn),即递归使用栈空间的空间代价。
class Solution {
public int findKthLargest(int[] nums, int k) {
int heapSize = nums.length;
buildMaxHeap(nums, heapSize);
for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
swap(nums, 0, i);
--heapSize;
maxHeapify(nums, 0, heapSize);
}
return nums[0];
}
public void buildMaxHeap(int[] a, int heapSize) {
for (int i = heapSize / 2; i >= 0; --i) {
maxHeapify(a, i, heapSize);
}
}
public void maxHeapify(int[] a, int i, int heapSize) {
int l = i * 2 + 1, r = i * 2 + 2, largest = i;
if (l < heapSize && a[l] > a[largest]) {
largest = l;
}
if (r < heapSize && a[r] > a[largest]) {
largest = r;
}
if (largest != i) {
swap(a, i, largest);
maxHeapify(a, largest, heapSize);
}
}
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
下面这个跟上面的一样
前 K 个高频元素
也是基于堆排和快排,,,做完上面题目再看下这个思路。
我自己实现的快排
第一次写,一堆坑。
前提:按从大到小排
根据快排思想,就是取某一个值,比它大的放左边,比它小的放右边,最后按照左中右一排;
但我走了很多坑:
1.只分了左右两部分,没有中间,导致比如最小的在最左,每次以第一个数作为比较 值,他会一直在最左,每次分两部分都是一样的,陷入死循环,,后改成三部分就好了。
2.左中右三部分 在分出来之前,,不知道有多少个,而数组初始化的时候就必须指定长度,故好多拷贝。
public int[] quickSort(int[] array){
if(Objects.isNull(array) || array.length ==0 ||array.length==1){
return array;
}
//取值
int mid = array[0];
int[] leftMore = new int[array.length];
int[] rightMore = new int[array.length];
int[] midMore = new int[array.length];
int leftMoreSize = 0;
int rightMoreSize = 0;
int midMoreSize = 0;
for(int a:array){
if(a>mid){
leftMore[leftMoreSize++] = a;
} else if (a<mid) {
rightMore[rightMoreSize++] = a;
} else {
midMore[midMoreSize++] = a;
}
}
int[] left = new int[leftMoreSize];
int[] right = new int[rightMoreSize];
int[] midOne = new int[midMoreSize];
System.arraycopy(leftMore,0,left,0,leftMoreSize);
System.arraycopy(rightMore,0,right,0,rightMoreSize);
System.arraycopy(midMore,0,midOne,0,midMoreSize);
int[] leftResult = quickSort(left);
int[] rightResult = quickSort(right);
int[] result = new int[array.length];
if(leftMoreSize > 0){
System.arraycopy(leftResult,0,result,0,leftMoreSize);
}
System.arraycopy(midOne,0,result,leftMoreSize,midMoreSize);
if(rightMoreSize >0){
System.arraycopy(rightResult,0,result,leftMoreSize+midMoreSize,rightMoreSize);
}
return result;
}
我楼上的做法有好多次拷贝,不好,网上拷贝了别人的,空间复杂度为O(1)
我当时写的思路,就是准备额外空间,将左中右分别放在自己的组里,然后对左右无序组再递归排序,这就需要额外空间了。人家的思路是:
取最左值为基准值,所有数据都在他的右侧,然后一个指针从右出发,一个从左出发,右边第一次遇到比他小的值,与左边第一个比他大的值,就交换,如果左右未相遇,就继续缩小范围的走(缩小包围圈,哈哈),每次遇到左大右小就交换,直到相遇,,那我们知道,相遇时,左边的都是比它小的,右边的都是比它大的,再将第一个位置的值也就是基准值与左指针交换位置,此时就形成了,基准值左边的比它小,右边的比它大。
下面的算法有个注意点:
这两个while的顺序,上面的while可能会多走一步,而下面的不会,所以交换的应该是下面的那个。
举个例子,如果两个while互换,以case:4,1,5为例,会造成i走到2位置,即5,这时候4和4互换,显然不对
/**
* 快速排序
* @param array
*/
public static void quickSort(int[] array) {
int len;
if(array == null
|| (len = array.length) == 0
|| len == 1) {
return ;
}
sort(array, 0, len - 1);
}
/**
* 快排核心算法,递归实现
* @param array
* @param left
* @param right
*/
public static void sort(int[] array, int left, int right) {
if(left > right) {
return;
}
// base中存放基准数
int base = array[left];
int i = left, j = right;
while(i != j) {
// 顺序很重要,先从右边开始往左找,直到找到比base值小的数
while(array[j] >= base && i < j) {
j--;
}
// 再从左往右边找,直到找到比base值大的数
while(array[i] <= base && i < j) {
i++;
}
// 上面的循环结束表示找到了位置或者(i>=j)了,交换两个数在数组中的位置
if(i < j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
// 将基准数放到中间的位置(基准数归位)
array[left] = array[i];
array[i] = base;
// 递归,继续向基准的左右两边执行和上面同样的操作
// i的索引处为上面已确定好的基准值的位置,无需再处理
sort(array, left, i - 1);
sort(array, i + 1, right);
}