大數據排序算法

一、快速排序(用的比較多)
(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;
				}
			}
		}
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章