数据结构与算法 Big O 备忘录与现实
不論今天的計算機技術變化,新技術的出現,所有都是來自數據結構與算法基礎。我們需要溫故而知新。
?????? 算法、架構、策略、機器學習之間的關系。在過往和技術人員交流時,很多人對算法和架構之間的關系感到不可理解,算法是軟的,架構是硬的,難道算法和架構還有什么關系不成?其實不然,算法和架構的關系非常緊密。在互聯網時代,我們需要用算法處理的數據規模越來越大,要求的處理時間越來越短,單一計算機的處理能力是不可能滿足需求的。而架構技術的發展,帶來了很多不同特點的分布式計算平臺。算法為了能夠應用到這些分布式計算平臺上,往往需要進化,例如:并行計算要求算法可以拆分為可并行計算的幾個獨立單位,但很多算法不具備這種可拆分特性,使得不能簡單通過分布式計算來提高效率。這時候,為了實現分布式化的計算效果,需要將算法進行等效改寫,使得其具有獨立拆分性。另一方面,算法的發展,也反過來會對計算架構提出新的要求。
?????? 對算法和策略的關系亦是,不過這兩個概念并不像算法和架構那樣好解釋。策略是解決具體問題的手段,而算法是解決一類問題的方法。解決一個具體問題,可能需要將問題分解為一個或者多個算法,一起作用來解決,也可能不需要算法。例如,對于個性化新聞,我們可能有一個策略是:重大新聞需要及時展現給用戶;而實現的具體算法可能只包括“重大新聞挖掘算法”等。
????? 機器學習是一類算法的統稱,在一定的數據集合上,利用機器學習的算法,自動得到規律,來進行預測,機器學習領域常見的問題包括分類問題、回歸問題等。而預測,尤其是對用戶的偏好進行預測是推薦領域的核心問題之一,機器學習算法在解決此類問題上會發生很大的作用。
- 沒有最好的算法,只有合適的算法。推薦算法和產品需求、應用場景、數據密切相關,不要相信有什么包打天下的算法;
- 數據是基礎:數據充足而且質量高,簡單算法也可以有不錯的效果;反之,則多好的算法也不可能有好的效果;
- 木桶效應:算法策略要和用戶需求、功能展現密切配合;(注:木桶原理又稱短板理論,其核心內容為“一只木桶盛水的多少,并不取決于桶壁上最高的那塊木塊,而恰恰取決于桶壁上最短的那塊。”)
- 一般而言,推薦算法都需要考慮是否能處理大數據,是否能大規模并行化。
?
正文
一、數據結構基礎
數組
定義
- 按順序連續存儲數據元素,通常索引從0開始
- 以集合論中的元組為基礎
- 數組是最古老,最常用的數據結構
知識要點
- 索引最優;不利于查找、插入和刪除(除非在數組最末進行)
- 最基礎的是線性數組或一維數組
數組長度固定,意味著聲明數組時應指明長度 - 動態數組與一維數組類似,但為額外添加的元素預留了空間
如果動態數組已滿,則把每一元素復制到更大的數組中 - 類似網格或嵌套數組,二維數組有 x 和 y 索引
時間復雜度
- O(1)索引:一維數組:O(1),動態數組:O(1)
- O(n)查找:一維數組:O(n),動態數組:O(n)
- O(log n)最優查找:一維數組:O(log n),動態數組:O(log n)
- O(n)插入:一維數組:n/a,動態數組:O(n)
鏈表
定義
- 結點存儲數據,并指向下一結點
最基礎的結點包含一個數據和一個指針(指向另一結點)- 鏈表靠結點中指向下一結點的指針連接成鏈
要點
- 為優化插入和刪除而設計,但不利于索引和查找
- 雙向鏈表包含指向前一結點的指針
- 循環鏈表是一種終端結點指針域指向頭結點的簡單鏈表
- 堆棧通常由鏈表實現,不過也可以利用數組實現
堆棧是“后進先出”(LIFO)的數據結構- 由鏈表實現時,只有頭結點處可以進行插入或刪除操作
- 同樣地,隊列也可以通過鏈表或數組實現
隊列是“先進先出”(FIFO)的數據結構- 由雙向鏈表實現時,只能在頭部刪除,在末端插入
時間復雜度
- O(n)索引:鏈表:O(n)
- O(n)查找:鏈表:O(n)
- Linked Lists: O(n)最優查找:鏈表:O(n)
- O(1)插入:鏈表:O(1)
哈希表或哈希圖
定義
- 通過鍵值對進行儲存
- 哈希函數接受一個關鍵字,并返回該關鍵字唯一對應的輸出值
這一過程稱為散列(hashing),是輸入與輸出一一對應的概念- 哈希函數為該數據返回在內存中唯一的存儲地址
要點
- 為查找、插入和刪除而設計
- 哈希沖突是指哈希函數對兩個不同的數據項產生了相同的輸出值
所有的哈希函數都存在這個問題- 用一個非常大的哈希表,可以有效緩解這一問題
- 哈希表對于關聯數組和數據庫檢索十分重要
時間復雜度
- O(1)索引:哈希表:O(1)
- O(1)查找:哈希表:O(1)
- O(1)插入:哈希表:O(1)
二叉樹
定義
- 一種樹形的數據結構,每一結點最多有兩個子樹
- 子結點又分為左子結點和右子結點
要點
- 為優化查找和排序而設計
- 退化樹是一種不平衡的樹,如果完全只有一邊,其本質就是一個鏈表
- 相比于其他數據結構,二叉樹較為容易實現
- 可用于實現二叉查找樹
- 二叉樹利用可比較的鍵值來確定子結點的方向
- 左子樹有比雙親結點更小的鍵值
- 右子樹有比雙親結點更大的鍵值
- 重復的結點可省略
- 由于上述原因,二叉查找樹通常被用作一種數據結構,而不是二叉樹
時間復雜度
- 索引:二叉查找樹:O(log n)
- 查找:二叉查找樹:O(log n)
- 插入:二叉查找樹:O(log n)
二、搜索基礎
廣度優先搜索
定義
- 一種在樹(或圖)中進行搜索的算法,從根結點開始,優先按照樹的層次進行搜索
- 搜索同一層中的各結點,通常從左往右進行
- 進行搜索時,同時追蹤當前層中結點的子結點
- 當前一層搜索完畢后,轉入遍歷下一層中最左邊的結點
- 最下層最右端是最末結點(即該結點深度最大,且在當前層次的最右端)
要點
- 當樹的寬度大于深度時,該搜索算法較優
- 進行樹的遍歷時,使用隊列存儲樹的信息
- 原因是:使用隊列比深度優先搜索更為內存密集
- 由于需要存儲指針,隊列需要占用更多內存
時間復雜度
- O(|E| + |V|)查找:廣度優先搜索:O(|E| + |V|)
- E 是邊的數目
- V 是頂點的數目
深度優先搜索
定義
- 一種在樹(或圖)中進行搜索的算法,從根結點開始,優先按照樹的深度進行搜索
- 從左邊開始一直往下遍歷樹的結點,直到不能繼續這一操作
- 一旦到達某一分支的最末端,將返回上一結點并遍歷該分支的右子結點,如果可以將從左往右遍歷子結點
- 當前這一分支搜索完畢后,轉入根節點的右子結點,然后不斷遍歷左子節點,直到到達最底端
- 最右的結點是最末結點(即所有祖先中最右的結點)
要點
- 當樹的深度大于寬度時,該搜索算法較優
- 利用堆棧將結點壓棧
- 因為堆棧是“后進先出”的數據結構,所以無需跟蹤結點的指針。與廣度優先搜索相比,它對內存的要求不高。
- 一旦不能向左繼續遍歷,則對棧進行操作
時間復雜度
- O(|E| + |V|)查找:深度優先搜索:O(|E| + |V|)
- E 是邊的數目
- V 是結點的數目
廣度優先搜索 VS. 深度優先搜索
- 這一問題最簡單的回答就是,選取何種算法取決于樹的大小和形態
- 就寬度而言,較淺的樹適用廣度優先搜索
- 就深度而言,較窄的樹適用深度優先搜索
細微的區別
- 由于廣度優先搜索(BFS)使用隊列來存儲結點的信息和它的子結點,所以需要用到的內存可能超過當前計算機可提供的內存(不過其實你不必擔心這一點)
- 如果要在某一深度很大的樹中使用深度優先搜索(DFS),其實在搜索中大可不必走完全部深度。可在 xkcd 上查看更多相關信息。
- 廣度優先搜索趨于一種循環算法。
- 深度優先搜索趨于一種遞歸算法
三、高效排序基礎
歸并排序
定義
- 一種基于比較的排序算法
- 將整個數據集劃分成至多有兩個數的分組
- 依次比較每個數字,將最小的數移動到每對數的左邊
- 一旦所有的數對都完成排序,則開始比較最左兩個數對中的最左元素,形成一個含有四個數的有序集合,其中最小數在最左邊,最大數在最右邊
- 重復上述過程,直到歸并成只有一個數據集
要點
- 這是最基礎的排序算法之一
- 必須理解:首先將所有數據劃分成盡可能小的集合,再作比較
時間復雜度
- O(n)最好的情況:歸并排序:O(n)
- 平均情況:歸并排序:O(n log n)
- 最壞的情況:歸并排序:O(n log n)
快速排序
定義
- 一種基于比較的排序算法
- 通過選取平均數將整個數據集劃分成兩部分,并把所有小于平均數的元素移動到平均數左邊
- 在左半部分重復上述操作,直到左邊部分的排序完成后,對右邊部分執行相同的操作
- 計算機體系結構支持快速排序過程
要點
- 盡管快速排序與許多其他排序算法有相同的時間復雜度(有時會更差),但通常比其他排序算法執行得更快,例如歸并排序。
- 必須理解:不斷通過平均數將數據集對半劃分,直到所有的數據都完成排序
時間復雜度
- O(n)最好的情況:歸并排序:O(n)
- O(n log n)平均情況:歸并排序:O(n log n)
- 最壞的情況:歸并排序:O(n2)
冒泡排序
定義
- 一種基于比較的排序算法
- 從左往右重復對數字進行兩兩比較,把較小的數移到左邊
- 重復上述步驟,直到不再把元素左移
要點
- 盡管這一算法很容易實現,卻是這三種排序方法中效率最低的
- 必須理解:每次向右移動一位,比較兩個元素,并把較小的數左移
時間復雜度
- O(n)最好的情況:歸并排序:O(n)
- O(n2)平均情況:歸并排序: O(n2)
- O(n2)最壞的情況:歸并排序: O(n2)
歸并排序 VS. 快速排序
- 在實踐中,快速排序執行速率更快
- 歸并排序首先將集合劃分成最小的分組,在對分組進行排序的同時,遞增地對分組進行合并
- 快速排序不斷地通過平均數劃分集合,直到集合遞歸地有序
四、算法類型基礎
遞歸算法
定義
- 在定義過程中調用其本身的算法
- 遞歸事件:用于觸發遞歸的條件語句
- 基本事件:用于結束遞歸的條件語句
要點
- 堆棧級過深和棧溢出
- 如果在遞歸算法中見到上述兩種情況中的任一個,那就糟糕了
- 那就意味著因為算法錯誤,或者問題規模太過龐大導致問題解決前 RAM 已耗盡,從而基本事件從未被觸發
- 必須理解:不論基本事件是否被觸發,它在遞歸中都不可或缺
- 通常用于深度優先搜索
迭代算法
定義
- 一種被重復調用有限次數的算法,每次調用都是一次迭代
- 通常用于數據集中遞增移動
要點
- 通常迭代的形式為循環、for、while和until語句
- 把迭代看作是在集合中依次遍歷每個元素
- 通常用于數組的遍歷
遞歸 VS. 迭代
- 由于遞歸和迭代可以相互實現,兩者之間的區別很難清晰地界定。但必須知道:
- 通常遞歸的表意性更強,更易于實現
- 迭代占用的內存更少
- (i.e. Haskell)函數式語言趨向于使用遞歸(如 Haskell 語言)
- 命令式語言趨向于使用迭代(如 Ruby 語言)
- 點擊 Stack Overflow post 了解更多詳情
遍歷數組的偽代碼(這就是為什么使用迭代的原因)
Recursion | Iteration
----------------------------------|----------------------------------
recursive method (array, n) | iterative method (array)
if array[n] is not nil | for n from 0 to size of array
print array[n] | print(array[n])
recursive method(array, n+1) |
else |
exit loop
貪婪算法
定義
- 一種算法,在執行的同時只選擇滿足某一條件的信息
- 通常包含5個部分,摘自維基百科:
- 候選集,從該集合中可得出解決方案
- 選擇函數,該函數選取要加入解決方案中的最優候選項
- 可行性函數,該函數用于決策某一候選項是否有助于解決方案
- 目標函數,該函數為解決方案或部分解賦值
- 解決方案函數,該函數將指明完整的解決方案
要點
- 用于找到預定問題的最優解
- 通常用于只有少部分元素能滿足預期結果的數據集合
- 通常貪婪算法可幫助一個算法降低時間 復雜度
偽代碼:用貪婪算法找到數組中任意兩個數字間的最大差值
greedy algorithm (array)
var largest difference = 0
var new difference = find next difference (array[n], array[n+1])
largest difference = new difference if new difference is > largest difference
repeat above two steps until all differences have been found
return largest difference
這一算法無需比較所有數字兩兩之間的差值,省略了一次完整迭代。
以下是Big O 核對表
Legend
| Excellent | Good | Fair | Bad | Horrible |
Data Structure Operations
| Data Structure | Time Complexity | ? | ? | ? | ? | ? | ? | ? | Space Complexity |
| ? | Average | ? | ? | ? | Worst | ? | ? | ? | Worst |
| ? | Access | Search | Insertion | Deletion | Access | Search | Insertion | Deletion | ? |
| Array | O(1) | O(n) | O(n) | O(n) | O(1) | O(n) | O(n) | O(n) | O(n) |
| Stack | O(n) | O(n) | O(1) | O(1) | O(n) | O(n) | O(1) | O(1) | O(n) |
| Singly-Linked List | O(n) | O(n) | O(1) | O(1) | O(n) | O(n) | O(1) | O(1) | O(n) |
| Doubly-Linked List | O(n) | O(n) | O(1) | O(1) | O(n) | O(n) | O(1) | O(1) | O(n) |
| Skip List | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(n) | O(n) | O(n) | O(n) | O(n log(n)) |
| Hash Table | - | O(1) | O(1) | O(1) | - | O(n) | O(n) | O(n) | O(n) |
| Binary Search Tree | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(n) | O(n) | O(n) | O(n) | O(n) |
| Cartesian Tree | - | O(log(n)) | O(log(n)) | O(log(n)) | - | O(n) | O(n) | O(n) | O(n) |
| B-Tree | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(n) |
| Red-Black Tree | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(n) |
| Splay Tree | - | O(log(n)) | O(log(n)) | O(log(n)) | - | O(log(n)) | O(log(n)) | O(log(n)) | O(n) |
| AVL Tree | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(n) |
Array Sorting Algorithms
| Algorithm | Time Complexity | ? | ? | Space Complexity |
| ? | Best | Average | Worst | Worst |
| Quicksort | O(n log(n)) | O(n log(n)) | O(n^2) | O(log(n)) |
| Mergesort | O(n log(n)) | O(n log(n)) | O(n log(n)) | O(n) |
| Timsort | O(n) | O(n log(n)) | O(n log(n)) | O(n) |
| Heapsort | O(n log(n)) | O(n log(n)) | O(n log(n)) | O(1) |
| Bubble Sort | O(n) | O(n^2) | O(n^2) | O(1) |
| Insertion Sort | O(n) | O(n^2) | O(n^2) | O(1) |
| Selection Sort | O(n^2) | O(n^2) | O(n^2) | O(1) |
| Shell Sort | O(n) | O((nlog(n))^2) | O((nlog(n))^2) | O(1) |
| Bucket Sort | O(n+k) | O(n+k) | O(n^2) | O(n) |
| Radix Sort | O(nk) | O(nk) | O(nk) | O(n+k) |
Graph Operations
| Node / Edge Management | Storage | Add Vertex | Add Edge | Remove Vertex | Remove Edge | Query |
| Adjacency list | O(|V|+|E|) | O(1) | O(1) | O(|V| + |E|) | O(|E|) | O(|V|) |
| Incidence list | O(|V|+|E|) | O(1) | O(1) | O(|E|) | O(|E|) | O(|E|) |
| Adjacency matrix | O(|V|^2) | O(|V|^2) | O(1) | O(|V|^2) | O(1) | O(1) |
| Incidence matrix | O(|V| ? |E|) | O(|V| ? |E|) | O(|V| ? |E|) | O(|V| ? |E|) | O(|V| ? |E|) | O(|E|) |
Heap Operations
| Type | Time Complexity | ? | ? | ? | ? | ? | ? |
| ? | Heapify | Find Max | Extract Max | Increase Key | Insert | Delete | Merge |
| Linked List (sorted) | - | O(1) | O(1) | O(n) | O(n) | O(1) | O(m+n) |
| Linked List (unsorted) | - | O(n) | O(n) | O(1) | O(1) | O(1) | O(1) |
| Binary Heap | O(n) | O(1) | O(log(n)) | O(log(n)) | O(log(n)) | O(log(n)) | O(m+n) |
| Binomial Heap | - | O(1) | O(log(n)) | O(log(n)) | O(1) | O(log(n)) | O(log(n)) |
| Fibonacci Heap | - | O(1) | O(log(n)) | O(1) | O(1) | O(log(n)) | O(1) |
Big-O Complexity Chart
?
計算機科學中最重要的32個算法
- 查找:判斷某特定元素屬于哪個組。
- 合并:聯合或合并兩個組為一個組。
現實中算法
Linux內核中的基本數據結構和算法
B+ 樹,代碼中的注釋將會告訴你一些教科書中不能學到的內容:
這是一個簡單的B+樹實現,我寫它的目的是作為練習,并以此了解B+樹的工作原理。結果該實現發揮了它的實用價值。
...
一個不經常在教科書中提及的技巧:最小值應該放在右側,而不是左側。一個節點內所有被使用的槽位應該在左側,沒有使用的節點應該為NUL,大部分的操作只遍歷一次所有的槽位,在第一個NUL處終止。
帶權重的有序列表用于互斥鎖、驅動等;
radix樹的一個常見的用法是保存頁面結構體的指針;
包含指針的只允許簡單插入的靜態大小優先級堆,基于CLR(算法導論)第七章
哈希函數,引用Knuth和他的一篇論文:
Knuth建議選擇與機器字長所能表達的最大整數約成黃金比例的素數來做乘法散列,Chuck Lever 證實了這個技術的有效性;
http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf
這些選擇的素數是位稀疏的,也就是說對他們的操作可以使用位移和加法來替換機器中很慢的乘法操作;
有些代碼,比如這個驅動,他們是自己實現的哈希函數
在命名空間樹中執行一個修改過的深度優先算法,開始(和終止于)start_handle所確定的節點。當與參數匹配的節點被發現以后,回調函數將會被調用。如果回調函數返回一個非空的值,搜索將會立即終止,這個值將會回傳給調用函數;
Knuth-Morris-Pratt 字符串匹配;
Knuth、Morris和 Pratt [1]實現了一個線性時間復雜度字符串匹配算法。該算法完全規避了對轉換函數DELTA的顯式計算。其匹配時間為O(n)(其中n是文本長度),只使用一個輔助函數PI[1...m](其中m是模式的長度),模式的預處理時間是O(m)。PI這個數組允許DELTA函數在需要時能迅速運行。大體上,對任意狀態q=0,1,...,m和任意SIGMA中的字符"a",PI["q"]保存了獨立于"a"的信息,并用于計算DELTA("q", "a")。由于PI這個數組只包含m個條目,而DELTA包含O(m|SIGMA|)個條目,我們通過計算PI進而在預處理時間保存|SIGMA|的系數,而非計算DELTA。
[1] Cormen, Leiserson, Rivest, Stein Introdcution to Algorithms, 2nd Edition, MIT Press
[2] See finite automation theory
Boyer-Moore模式匹配,如下是引用和對其他算法的使用建議;
Boyer-Moore字符串匹配算法:
[1] A Fast String Searching Algorithm, R.S. Boyer and Moore. Communications of the Association for Computing Machinery, 20(10), 1977, pp. 762-772. http://www.cs.utexas.edu/users/moore/publications/fstrpos.pdf
[2] Handbook of Exact String Matching Algorithms, Thierry Lecroq, 2004 http://www-igm.univ-mlv.fr/~lecroq/string/string.pdf
注意:由于Boyer-Moore(BM)自右向左做匹配,有一種可能性是一個匹配分布在不同的塊中,這種情況下是不能找到任何匹配的。
如果你想確保這樣的事情不會發生,使用Knuth-Pratt-Morris(KMP)算法來替代。也就是說,根據你的設置選擇合適的字符串查找算法。
如果你使用文本搜索架構來過濾、網絡入侵檢測(NIDS)或者任何安全為目的,那么選擇KMP。如果你關乎性能,比如你在分類數據包,并應用服務質量(QoS)策略,并且你不介意可能需要在分布在多個片段中匹配,然后就選擇BM。
Chromium 瀏覽器中的數據結構和算法
此樹會被分配策略參數化,這個策略負責在C的自由存儲空間和區域中分配列表,參見zone.h
同時,代碼中還包含了一些第三方的算法和數據結構,例如:
編程語言類庫
分配和調度算法
*nix系統中的核心組件
加密算法
編譯器
壓縮和圖片處理
為GIF圖片格式而出現的Lempel-Zivsraf算法在圖片處理程序中經常被應用,從一個簡單的*nix組件轉化為一個復雜的程序;
運行長度編碼被用于生成PCX文件(用于Paintbrush這個程序中),壓縮BMP文件和TIFF文件;
小波壓縮(Wavelet壓縮)是JPEG 2000的基礎,所以所有生成JPEG 2000文件的數碼相機都是實現了這個算法;
Reed-Solomon糾錯用于Linux內核、CD驅動、條形碼讀取,并且結合卷積從航行團隊進行圖片傳輸;
沖突驅動條款學習算法(Conflict Driven Clause Learning)
自2000年以來,在工業標準中的SAT(布爾滿足性問題)求解器的運行時間每年都在成倍減少。這一發展的一個非常重要的原因是沖突驅動條款學習算法(Conflict Driven Clause Learning)的使用,它結合了Davis Logemann和Loveland的約束編程和人工智能研究技術的原始論文中關于布爾約束傳播的算法。具體來說,工業建模中SAT被認為是一個簡單的問題(見討論)。對我來說,這是近代最偉大的成功故事之一,因為它結合了先進的算法、巧妙的設計思路、實驗反饋,并以一致的共同努力來解決這個問題。Malik和Zhang的CACM論文是一個很好的閱讀材料。許多大學都在教授這個算法,但通常是在邏輯或形式化方法的課程中。
?
?
希望對您企業應用開發與企業信息化有幫助。 其它您可能感興趣的文章:
《視覺直觀感受 7 種常用的排序算法》
《匈牙利 Sapientia 大學的 6 種排序算法舞蹈視頻》
《視頻:6 分鐘演示 15 種排序算法》
《SORTING:可視化展示排序算法的原理,支持單步查看》
《VisuAlgo:通過動畫學習算法和數據結構》
軟件開發的專業化
IT基礎架構規劃方案一(網絡系統規劃)
IT基礎架構規劃方案二(計算機系統與機房規劃規劃)?
IT基礎架構規劃方案三(IT基礎軟件和系統規劃)
企業應用之性能實時度量系統演變
云計算參考架構幾例
智能移動導游解決方案簡介
人力資源管理系統的演化
如有想了解更多軟件研發 , 系統 IT集成 , 企業信息化 等資訊,請關注我的微信訂閱號:
作者:Petter Liu
出處:http://www.cnblogs.com/wintersun/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
該文章也同時發布在我的獨立博客中-Petter Liu Blog。
轉載于:https://www.cnblogs.com/wintersun/p/4840585.html
總結
以上是生活随笔為你收集整理的数据结构与算法 Big O 备忘录与现实的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: QQ聊天文字背影图片拉伸方法
- 下一篇: java 添加一个线程、创建响应的用户界