數據結構複習
文章目錄
排序
名次排序/計數排序/原地重排
名次計算
名次是所有比它小的元素個數加上在它左邊出現的與它相同的元素個數。
對於每個i
的值,比較次數爲i
,因此總的比較次數爲1+2+3+...+n-1=(n-1)n/2
.
template<class T>
void rank(T a[], int n, int r[]) {
for(int i = 1; i < n; ++i)
r[i] = 0;
for(int i = 1; i < n; ++i)
for(int j = 0; j < i; ++j)
if(a[j] <= a[i])
r[i] ++;
else r[j] ++;
}
名次排序
利用附加數組的計數排序
2*n
次移動+之前的(n-1)n/2
次比較。
template<class T>
void rearrange(T a[], int n, int r[]) {
T *u = new T[n];
for(int i = 0; i < n; ++i)
u[r[i]] = a[i];
for(int i = 0; i < n; ++i)
a[i] = u[i];
delete[] u;
}
原地重排
交換次數從最少0
(初始即有序)到最多2(n-1)
.每次交換都可以將一個a[i]
移動到正確位置。
比前者空間減少,時間複雜度增加一丟丟。
temlate<class T>
void rearrange(T a[], int n, int r[]) {
for(int i = 0; i < n; ++i)
while(r[i] != i) {
int t = r[i];
swap(a[i], a[t]);
swap(r[i], r[t]);
}
}
選擇排序
依次找剩下的元素中的最大值。
總的比較次數(n-1)n/2
,元素移動次數3(n-1)
.
template<class T>
void selectionSort(T a[], int n) {
for(int size = n; size > 1; size--) {
int j = indexOfMax(a, size);
swap(a[j], a[size-1]);
}
}
及時終止的選擇排序
檢查如果已經有序,那麼就沒有必要繼續了。
template<class T>
void selectionSort(T a[], int n) {
bool sorted = false;
for(int size = n; !sorted && (size > 1); size--) {
int indexOfMax = 0;
sorted = true;
for(int i = 1; i < size; i++)
if(a[indexOfMax] <= a[i])
indexOfMax = i;
else sorted = false;
swap(a[indexOfMax], a[size-1]);
}
}
及時終止的冒泡排序
時間複雜度也爲(n-1)n/2
,最好情況n-1
。
template<class T>;
bool bubble(T a[], int n) {
bool swapped = false;
for(int i = 0; i < n-1; i++)
if(a[i] > a[i+1]) {
swapped = true;
swap(a[i], a[i+1]);
}
return swapped;
}
template<class T>;
void bubbleSort(T a[], int n) {
for(int i = n; (i > 1) && bubble(a,i); i--);
}
插入排序
最好的比較次數是n-1
,最壞的比較次數是(n-1)n/2
.
template<class T>
void insert(T a[], int n, const T& x) {
int i;
for(i = n-1; i >= 0 && x < a[i]; i--)
a[i+1] = a[i];
a[i+1] = x;
}
template<class T>
void insertion(T a[], int n) {
for(int i = 1; i < n; ++i) {
T t = a[i];
insert(a, i, t);
}
}
版本2,其實沒啥不同
template<class T>
void insertion(T a[], int n) {
for(int i = 1; i < n; ++i) {
T t = a[i];
int j;
for(j = i-1; j >= 0 && t < a[j]; --j)
a[j+1] = a[j];
a[j+1] = t;
}
}
堆 & 堆排序
通過初始化也可以排序,初始化好快啊,O(n)
!(也就找個最大元素,其實理應這麼快)
void init() {
for(int root = heapSize/2; root >= 1; --root) {
int rootValue = heap[root];
int child = 2*root;
while(child <= heapSize) {
if(child < heapSize && heap[child] < heap[child+1])
child ++;
if(rootValue >= heap[child])
break;
heap[child/2] = heap[child];
child *= 2;
}
heap[child/2] = rootValue;
}
}
通過插入刪除,都是O(logn)
void insert(int x) {
int nw = ++heapSize;
while(nw != 1 && heap[nw/2] < x) {//父節點更小 需要下移
heap[nw] = heap[nw/2]; //下移
nw /= 2;
}
heap[nw] = x;
}
void pop() {
int last = heap[heapSize--]; //取出最後一個元素
int nw = 1, child = 2;
while (child <= heapSize) {
if(child < heapSize && heap[child] < heap[child+1])
child ++;
if(last >= heap[child]) // 比孩子們都大
break;
heap[nw] = heap[child]; //孩子挪上來
nw = child;
child *= 2;
}
heap[nw] = last;
}
歸併排序 分治
書上代碼看不懂。
C語言版賊好用而且短好喜歡這個,思想是一樣的,考試考思路沒問題。
樹形 複雜度O(nlogn)
.
比較好的教程:常用12大排序算法之八:(遞歸和非遞歸)歸併排序
void Merge(int sourceArr[],int tempArr[], int startIndex, int midIndex, int endIndex) {
int i = startIndex, j=midIndex+1, k = startIndex;
while(i!=midIndex+1 && j!=endIndex+1) {
if(sourceArr[i] > sourceArr[j])
tempArr[k++] = sourceArr[j++];
else
tempArr[k++] = sourceArr[i++];
}
while(i != midIndex+1)
tempArr[k++] = sourceArr[i++];
while(j != endIndex+1)
tempArr[k++] = sourceArr[j++];
for(i=startIndex; i<=endIndex; i++)
sourceArr[i] = tempArr[i];
}
void MergeSort(int sourceArr[], int tempArr[], int startIndex, int endIndex) {
int midIndex;
if(startIndex < endIndex) {
midIndex = (startIndex + endIndex) / 2;
MergeSort(sourceArr, tempArr, startIndex, midIndex);
MergeSort(sourceArr, tempArr, midIndex+1, endIndex);
Merge(sourceArr, tempArr, startIndex, midIndex, endIndex);
}
}
快速排序 分治
思想,選取一個支點n,比n小的元素放在n的左邊作爲左段,比n大的放在n的右邊作爲右段,然後分別在左段和右段中進行快排,最後元素序列是左段+n+右段
便是有序0。
見過的最通俗易懂的代碼。時間複雜度O(nlogn)
,最壞複雜度O(n^2)
.
void qsort(int a[], int left_index, int right_index)
{
int left, right, pivot;
if(left_index >= right_index)
return;
left = left_index;
right = right_index;
pivot = a[(left_index + right_index) /2];
while(left <= right) {
while(a[left] < pivot) left++;
while(a[right] > pivot) right--;
if(left <= right) {
swap(a[left],a[right]);
left++; right--;
}
}
qsort(a,left_index,right);
qsort(a,left,right_index);
}
箱子排序
即桶排。複雜度O(n)
.
基數排序
時間複雜度O(n),但是有個大常數c,所以可能不如快排快。
把數的每一位分離出來,從個位到高位依次桶排序一遍。
時間複雜度O(n)
,有個大常數。
排序總結
穩定排序
一個排序方法能夠保持同值元素之間的相對次序,那麼該方法稱爲穩定排序。
基數排序、冒泡排序、直接插入排序、折半插入排序、歸併排序是穩定的排序算法。
穩定:插入排序、冒泡排序、歸併排序、計數排序、基數排序、桶排序
不穩定:快速排序、堆排序、希爾排序、選擇排序
冒泡排序 | 平均性能 | 最壞情況下性能 | 最好情況下性能 | 穩定性 |
---|---|---|---|---|
冒泡排序 | n^2 |
n^2 |
n |
穩定 |
計數排序 | n^2 |
n^2 |
n^2 |
穩定 |
插入排序 | n^2 |
n^2 |
n |
穩定 |
選擇排序 | n^2 |
n^2 |
n^2 |
穩定 |
堆排序 | nlogn |
nlogn |
nlogn |
不穩定 |
歸併排序 | nlogn |
nlogn |
nlogn |
穩定 |
快速排序 | n^2 |
nlogn |
nlogn |
不穩定 |
copy函數的三個參數
copy(a, a+n, tmp)
表示從a
到tmp
.
漸進記法
沒太看懂。估計不考。
散列表
線性探查
如果哈希的地方已經有元素,那麼找下一個可用桶。
要會畫圖模擬。
比較難的刪除操作代碼,一次檢查每個桶。
template<class T>
int hashTable<T>::erase(const T& theElement) {
int root = search(theElement);
delete table[root];
table[root] = NULL;
int cnt = 0;
int i = (root+1)%divisor;
int pre = (i-1+divisor)%divisor;
while(table[i] != NULL && i != root) {
int thisfather = (*table[i])%divisor;
if((thisfather!= i)&&(((thisfather<=pre)&&(i>pre))||((thisfather>i)&&((pre<i)||(pre>=thisfather))))) {
table[pre] = table[i];
table[i] = NULL;
pre = i;
cnt++;
}
i = (i+1)%divisor;
}
return cnt;
}
鏈式散列
比較簡單。要會畫圖。
負載因子
對於線性探查:
一次成功搜索
不成功搜索
一般情況下小於0.75.
性能
線性探查空間少,平均性能鏈式散列快。
樹
樹的還原
前序遍歷:根左右
中序遍歷:左根右
後序遍歷:左右根
根據前中序列,求後序
根據前序遍歷劃分中序遍歷
中後求前也類似,要注意的是中後的時候會倒序看根節點會先右再左。
huffman樹
建立過程,每次取出兩棵權重最小的二叉樹。最後獲得編碼。
WEP是所有的路徑長*頻率再求和,也就是最終編碼的位數。
AVL樹
可視化 https://www.cs.usfca.edu/~galles/visualization/AVLtree.html
LL LR RR RL
B-樹
課本上的插入刪除很棒。
圖
拓撲排序
有向無環圖(DAG)纔有拓撲排序。
-
從 DAG 圖中選擇一個 沒有前驅(即入度爲0)的頂點並輸出。
-
從圖中刪除該頂點和所有以它爲起點的有向邊。
-
重複 1 和 2 直到當前的 DAG 圖爲空或當前圖中不存在無前驅的頂點爲止。後一種情況說明有向圖中必然存在環。
Dijkstra
過程,要所有已知的最短邊去更新鄰接點。
正確性:當前節點能夠被取出來說明已經沒有其他邊可以更新此邊了。
Prim
記錄哪些點已經在生成樹中
當前生成樹的所有鄰接邊中選最小的
矩陣
特殊矩陣的壓縮
對角矩陣
三對角矩陣(分類討論就可)
三角矩陣(n(n+1)/2
),下三角矩陣
稀疏矩陣
線性儲存
相加
行主索引判斷一下前後次序,掃描一遍,小的加進去,相等就相加,直到其中一個到了末尾,最後剩下的元素推進去。
相乘
從i,k
中 尋找 k,j
,進行累加。 相當於ans[i][j]
要枚舉中間元素k
。