一、快速排序(用的比較多)
(1) 遞歸對所有數據分成[a,b)b(b,d]兩個區間,(b,d]區間內的數都是大於[a,b)區間內的數
(2) 對(b,d]重複(1)操作,直到最右邊的區間個數小於1000個。注意[a,b)區間不用劃分
(3) 返回上一個區間,並返回此區間的數字數目。接着方法仍然是對上一區間的左邊進行劃分,分爲[a2,b2)b2(b2,d2]兩個區間,取(b2,d2]區間。如果個數不夠,繼續(3)操作,如果個數超過1000的就重複1操作,直到最後右邊只有1000個數爲止。
二、分塊查找
先把100w個數分成100份,每份1w個數。先分別找出每1w個數裏面的最大的數,然後比較。找出100個最大的數中的最大的數和最小的數,取最大數的這組的第二大的數,與最小的數比較。。。。
三、堆排序(針對大數據使用較多)
先取出前1000個數,維護一個1000個數的最小堆,遍歷一遍剩餘的元素,在此過程中維護堆就可以了。具體步驟如下:
- step1:取前m個元素(例如m=100),建立一個小頂堆。保持一個小頂堆得性質的步驟,運行時間爲O(lgm);建立一個小頂堆運行時間爲m*O(lgm)=O(m lgm);
- step2:順序讀取後續元素,直到結束。每次讀取一個元素,如果該元素比堆頂元素小,直接丟棄,如果大於堆頂元素,則用該元素替換堆頂元素,然後保持最小堆性質。最壞情況是每次都需要替換掉堆頂的最小元素,因此需要維護堆的代價爲(N-m)*O(lgm);
- 最後這個堆中的元素就是最大的1000個。時間複雜度爲O(N lgm)。
補充:這個方法的說法也可以更簡化一些:
假設數組arr保存1000個數字,首先取前1000個數字放入數組arr,對於第1001個數字k,如果k大於arr中的最小數,則用k替換最小數,對剩下的數字都進行這種處理。
排序一般都是拿空間換時間的。
題目:如何在10億數中找出前1000大的數?
小史:我可以用分治法,這有點類似快排中partition的操作。隨機選一個數t,然後對整個數組進行partition,會得到兩部分,前一部分的數都大於t,後一部分的數都小於t。
小史:如果說前一部分總數大於1000個,那就繼續在前一部分進行partition尋找。如果前一部分的數小於1000個,那就在後一部分再進行partition,尋找剩下的數。
小史:首先,partition的過程,時間是o(n)。我們在進行第一次partition的時候需要花費n,第二次partition的時候,數據量減半了,所以只要花費n/2,同理第三次的時候只要花費n/4,以此類推。而n+n/2+n/4+...顯然是小於2n的,所以這個方法的漸進時間只有o(n)
(注:這裏的時間複雜度計算只是簡化計算版,真正嚴謹的數學證明可以參考算法導論相關分析。)
半分鐘過去了。
小史一時慌了神。
他回憶起了之前呂老師給他講解bitmap時的一些細節。突然有了一個想法。
小史在紙上畫了畫。
推排序定義:
在描述算法複雜度時,經常用到O(1), O(n), O(logn), O(nlogn)來表示對應複雜度程度, 不過目前大家默認也通過這幾個方式表示空間複雜度 。那麼,O(1), O(n), O(logn), O(nlogn)就可以看作既可表示算法複雜度,也可以表示空間複雜度。
大O加上()的形式,裏面其實包裹的是一個函數f(),O(f()),指明某個算法的耗時/耗空間與數據增長量之間的關係。其中的n代表輸入數據的量。
推排序
堆排序是利用堆這種數據結構而設計的一種排序算法,堆排序是一種選擇排序,它的最壞,最好,平均時間複雜度均爲O(nlogn),它也是不穩定排序。首先簡單瞭解下堆結構。
堆
堆是具有以下性質的完全二叉樹:每個結點的值都大於或等於其左右孩子結點的值,稱爲大頂堆;或者每個結點的值都小於或等於其左右孩子結點的值,稱爲小頂堆。如下圖:
堆排序從1億個數中找到最小的100個數
package com.test;
import java.util.Date;
import java.util.Arrays;
import java.util.Random;
public class SortClass {
public static void main(String[] args) {
find();
}
public static void find() {//
int number = 100000000;// 一億個數
int maxnum = 1000000000;// 隨機數最大值
int i = 0;
int topnum = 100;// 取最大的多少個
Date startTime = new Date();
Random random = new Random();
int[] top = new int[topnum];
for (i = 0; i < topnum; i++) {
top[i] = Math.abs(random.nextInt(maxnum));// 設置爲隨機數
// top[i] = getNum(i);
}
buildHeap(top, 0, top.length);// 構建最小堆, top[0]爲最小元素
for (i = topnum; i < number; i++) {
int currentNumber2 = Math.abs(random.nextInt(maxnum));// 設置爲隨機數
// int currentNumber2 = getNum(i);
// 大於 top[0]則交換currentNumber2 重構最小堆
if (top[0] < currentNumber2) {
top[0] = currentNumber2;
shift(top, 0, top.length, 0); // 構建最小堆 top[0]爲最小元素
}
}
// System.out.println(Arrays.toString(top));
sort(top);
System.out.println("\n"+Arrays.toString(top)+"\n");
Date endTime = new Date();
System.out.println("用了" + (endTime.getTime() - startTime.getTime()) + "毫秒");
}
public static int getNum(int i) {
return i;
}
// 構造排序數組
public static void buildHeap(int[] array, int from, int len) {
int pos = (len - 1) / 2;
for (int i = pos; i >= 0; i--) {
shift(array, from, len, i);
}
}
/**
* @param array top數組
* @param from 開始
* @param len 數組長度
* @param pos 當前節點index
*/
public static void shift(int[] array, int from, int len, int pos) {
// 保存該節點的值
int tmp = array[from + pos];
int index = pos * 2 + 1;// 得到當前pos節點的左節點
while (index < len)// 存在左節點
{
if (index + 1 < len && array[from + index] > array[from + index + 1])// 如果存在右節點
{
// 如果右邊節點比左邊節點小,就和右邊的比較
index += 1;
}
if (tmp > array[from + index]) {
array[from + pos] = array[from + index];
pos = index;
index = pos * 2 + 1;
} else {
break;
}
}
// 最終全部置換完畢後 ,把臨時變量賦給最後的節點
array[from + pos] = tmp;
}
public static void sort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
// 當前值當作最小值
int min = array[i];
for (int j = i + 1; j < array.length; j++) {
if (min > array[j]) {
// 如果後面有比min值還小的就交換
min = array[j];
array[j] = array[i];
array[i] = min;
}
}
}
}
}