[1].從冒泡排序和快速排序引入算法
[2].時間複雜度的引入
[3].空間複雜度的引入
[4].數據結構和算法之間的雜談
複製代碼
輸入: 原生可用數據 = 數據獲取 + 數據解析
處理:邏輯加工(算法核心)
輸出:得到預期數據
拿一個排序算法來講:[輸入原始雜亂數據,經過邏輯加工,生成預期有序數據]
複製代碼
100W個隨機數,存儲到文件中,使用時解析數據造成int數組
問: 爲何要存到文件裏,直接在內存裏不就好了嗎?
---- 數據固化以後,保證原始數據不變且容易查看和再加工python
排序以前的前3000個
排序以前的前3000個
數據的來源能夠多種多樣,這裏用最簡單的方式生成大批量數據,隨機100W個0~100W的數字算法
public class NumMaker {
public static void main(String[] args) throws IOException {
String path = "J:\\sf\\data\\num.txt";
int bound = 100 * 10000;
initData(path, bound);
}
/**
* 初始化數據
*
* @param path 文件路徑
* @param bound 數據個數
* @throws IOException
*/
private static void initData(String path, int bound) throws IOException {
Random random = new Random();
FileHelper.mkFile(path);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bound; i++) {
sb.append(random.nextInt(bound));
if (i != bound - 1) {
sb.append(",");
}
}
FileWriter writer = new FileWriter(path);
writer.write(sb.toString());
writer.close();
}
}
/**
* 建立文件
* @param path 文件路徑
* @return 文件是否被建立
*/
public static boolean mkFile(String path) {
boolean success = true;
File file = new File(path);//1.建立文件對象
if (file.exists()) {//2.判斷文件是否存在
return false;//已存在則返回
}
File parent = file.getParentFile();//3.獲取父文件
if (!parent.exists()) {
if (!parent.mkdirs()) {//4.建立父文件
return false;
}
}
try {
success = file.createNewFile();//5.建立文件
} catch (IOException e) {
success = false;
e.printStackTrace();
}
return success;
}
複製代碼
/**
* 解析原始數據,獲得int數組
* @param path 路徑
* @return int數組
*/
private static int[] parseData(String path) throws IOException {
FileReader reader = new FileReader(path);
StringBuilder sb = new StringBuilder();
int len = 0;
char[] buf = new char[1024];
while ((len = reader.read(buf)) != -1) {
sb.append(new String(buf, 0, len));
}
String[] data = sb.toString().split(",");
//String數組轉int
int[] ints = new int[data.length];
for (int i = 0; i < ints.length; i++) {
ints[i] = Integer.parseInt(data[i]);
}
return ints;
}
複製代碼
/**
* 冒泡排序
* @param arr 數組
* @param n 個數
*/
private static void bubbleSort(int arr[], int n) {
int i, j, t;
// 要遍歷的次數,第i趟排序
for (i = 1; i < n - 1; i++) {
for (j = 0; j < n - 1; j++) {
// 若前者大於後者,則交換
if (arr[j] > arr[j + 1]) {
t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
}
}
}
}
/**
* 快速排序
*
* @param arr 數組
* @param start 起點
* @param end 重點
*/
private static void fastSort(int[] arr, int start, int end) {
int i, j, key;
if (start >= end) {
return;
}
i = start + 1;
j = end;
key = arr[start];//基準位
while (i < j) {
while (key <= arr[j] && i < j) j--; //←--
while (key >= arr[i] && i < j) i++; //--→
if (i < j) {//交換
int t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
arr[start] = arr[i];//交換基準位
arr[i] = key;
fastSort(arr, start, j - 1);//左半
fastSort(arr, j + 1, end);//右半
}
複製代碼
//使用冒泡排序
// System.out.println("bubbleSort開始-----------------------");
// long start = System.currentTimeMillis();
// bubbleSort(data, data.length);
// long end = System.currentTimeMillis();
// System.out.println("bubbleSort耗時:" + (end - start) / 1000.f + "秒");
//使用快速排序
System.out.println("fastSort開始-----------------------");
long start = System.currentTimeMillis();
fastSort(data, 0, data.length-1);
long end = System.currentTimeMillis();
System.out.println("fastSort耗時:" + (end - start) / 1000.f + "秒");
String path_sort = "J:\\sf\\data\\num_sort.txt";
saveInts(data, path_sort);//將結果保存到文件
複製代碼
用python的matplotlib,就這麼簡單
因爲100W條數據太多,渲染太慢,就算渲染出來也一片糊,這裏取前3000個感覺一下。數組
import matplotlib.pyplot as plt
def init_data():
data_raw = open("J:\\sf\\data\\num_raw.txt").readline()
data = data_raw.split(",")
data = list(map(int, data)) # 將字符型列表轉爲int型
for i in range(2000, 3000): # 查看的數據索引範圍
plt.scatter(i, data[i], alpha=0.6)
if __name__ == '__main__':
init_data()
plt.show() # 顯示所畫的圖
複製代碼
冒泡排序和使用快速bash
冒泡排序排列:
fastSort開始-----------------------
等了一個小時都沒排出來,算了,不等了,我就點了暫停...
使用快速排序:
fastSort開始-----------------------
fastSort耗時:0.216秒
這TM是"愚公移山"和"沉香劈山"的差距啊...,短短的幾行代碼,都是智慧的結晶。
等兩個小時都排不出來和 0.216秒 完成任務,這就是算法帶來的價值
複製代碼
這時你也許會問:兩種排序的差距爲什麼如此巨大,且聽下面細細分解。數據結構
描述算法運行時間和輸入數據之間的關係
加法+賦值
的耗時:System.out.println("fastSort開始-----------------------");
long start = System.nanoTime();
for (int i = 0; i < 100000000; i++) {
int a = 1 + i;
}
long end = System.nanoTime();
System.out.println("fastSort耗時:" + (end - start) + "納秒");
結果在: 6324942 納秒左右 即:6.324942 ms (1 ns = 100 0000 ms)
複製代碼
CPU的主頻:即CPU內核工做的時鐘頻率,例如個人筆記本是2.20GHz
頻率(Hz):描述週期性循環信號(包括脈衝信號)在單位時間內所出現的脈衝數量
1GHz=1000MHz,1MHz=1000kHz,1kHz=1000Hz
2.20GHz = 2200 MHz = 2200 000 kHz = 2200 000 000 Hz 即22億Hz
即一秒鐘內CPU脈衝震盪次數爲 22 億次 ,因爲執行某指令須要多個時鐘週期
但因爲不一樣指令所需的週期數是不定的,具體1s能執行多少次指令很難量化
因而一個算法的時間複雜度應運而生,其中理想化了一個計算模型:
1.標準的簡單指令系統:運算與賦值等
2.模型機處理簡單指令的都剛好花費1個時間單位
複製代碼
/**
* 冒泡排序
* @param arr 數組
* @param n 個數
*/
private static void bubbleSort(int arr[], int n) {
int i, j, t;
// 要遍歷的次數,第i趟排序
for (i = 1; i < n - 1; i++) {
for (j = 0; j < n - 1; j++) {
// 若前者大於後者,則交換
if (arr[j] > arr[j + 1]) {
t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
}
}
}
}
其外層循環執行 N - 1 次。
內層循環最多的時候執行N次,最少的時候執行1次,平均執行 (N+1)/2 次。
因此循環體內的[交換邏輯]約執行:(N - 1)(N + 1) / 2 = (N^2 -1)/2 次。
按照計算複雜度的原則,去掉常數,去掉最高項係數,其複雜度爲O(N^2)。
也就是說100W條數據, 最多要執行 100W*100W(即100億) 次交換邏輯
測試了一下一次交換邏輯平均耗時500納秒左右
因此總耗時: 500 * 100億 ms = 5000 秒 = 1.3888889 時
複製代碼
我在交換時放了一個count計數,最終:
count = 3919355 遠比冒泡排序要少
app
/**
* 快速排序
*
* @param arr 數組
* @param start 起點
* @param end 重點
*/
private static void fastSort(int[] arr, int start, int end) {
int i, j, key;
if (start >= end) {
return;
}
i = start + 1;
j = end;
key = arr[start];//基準位
while (i < j) {
while (key <= arr[j] && i < j) j--; //←--
while (key >= arr[i] && i < j) i++; //--→
if (i < j) {//交換
int t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
arr[start] = arr[i];//交換基準位
arr[i] = key;
fastSort(arr, start, j - 1);//左半
fastSort(arr, j + 1, end);//右半
}
快速排序時間複雜度是:nlogn , 即平均執行 100W*log100W ≈ 20 * 100W = 2000W 次 ,
快速排序時間複雜度的計算這裏暫時就不分析了,後面會有專題
複製代碼
描述算法消耗臨時空間和輸入數據之間的關係
暫略dom
數據結構離不開算法分析,結構自己是對現實中問題的抽象,算法使之呈現
算法雖然能夠獨立於結構存在,但數據結構可使之絢麗多彩,變幻莫測
複製代碼
其實我更願意將數據和結構分開來講:
數據是應用程序存在和生存必不可少的部分,就像化學元素之於生物
而結構給了數據更好的承載體,複雜而優秀的結構有利於物種的存在與支配資源,就像人類之於酵母菌
一個生物的DNA結構決定了它的形貌,一個生物的骨架決定了它有何優點,如何生存。
在我眼中結構是天然的,純正的。而數據會附和與結構造成一種美妙的狀態
複製代碼
坦白說,個人算法很渣,但我喜歡分析和計算,我一直以爲,算法和計算是兩個不一樣的概念
計算是數學的,會依賴數學公式,特別是一些圖形相、繪製相關的計算
但算法給人感受很深沉或說深奧,並且條條大路通羅馬,須要分析優劣
算法最令我失落的是:
我能夠一字不落背下它,能夠debug一步一步理解它,能夠畫圖去演示它,
但我不想到它爲什麼存在,第一個設計它的人是怎麼想的,這種感受讓我很難受。
複製代碼
一開始接觸隊列時感受so easy,不就是入隊,出隊,查看隊首嗎?
鏈表不就是一個一個接起來,這有什麼難的?你知道一件事物是什麼和你能運用它創造價值是天壤之別
當看到阻塞隊列和信息隊列,感受本身是多麼無知
也許能夠硬記背出紅黑樹的特色,甚至實現的細節,若是不去思考一個算法爲何存在,
那它也許只是你腦子裏的一團乾草,沒有營養並且佔用空間。
由於算法中的巧妙之處太多太多,一深究就StackOver(棧溢出),致使我一直避讓着算法,可是:
複製代碼
結構是支撐人體存在的骨架
數據是附着在骨架上的血液與肉體
算法是支配骨架和血肉的靈魂
複製代碼