1. 程式人生 > 其它 >HFUU資料結構期末考試考點整理

HFUU資料結構期末考試考點整理

2021年HFUU資料結構期末考試

一.演算法設計題(30分)

1.有序的順序表合併

大概的步驟就是用三個指標ia ib ic分別指向三個順序表,然後比較順序表A B的當前的值的大小,把小的或者相等的存到順序表C中,再移動對應的指標,直到順序表A B中所有的元素都進入順序表C。

//按照課本來的,資料從data陣列下標為0開始存,所以last是順序表最後一個元素的下標,表長為 list -> last + 1
#define maxlen 100
typedef struct
{
    int data[maxlen];
    int last;
}Orderlist;
//基於以上的定義方式進行順序表合併,考試的時候老師會把結構體給你的
Orderlist *Qmerge(Orderlist *A, Orderlist *B)
{
    Orderlist *C;//合併至順序表C
    int ia = 0, ib = 0, ic = 0;//三個指標分別指向三個順序表的當前位置
    while(ia != A -> last && ib != B -> last)
    {
        if(A -> data[ia] < B -> data[ib]) 
            C -> data[ic] = A -> data[ia ++];
        else if(A -> data[ia] > B -> data[ib])
            C -> data[ic] = B -> data[ib ++]; 
        else 
        {
            C -> data[ic] = B -> data[ib ++];
            ia ++;
        }
        ic ++;
    }
    while(ia != A -> last) C -> data[ic ++ ] = A -> data[ia ++ ];
    while(ib != B -> last) C -> data[ic ++ ] = B -> data[ib ++ ];
    C -> last = ic - 1;//最後一步合併ic多自加了一次
    return C;
}

2.有序的連結串列的合併

特點:

不佔用新的儲存空間,直接合並,時間複雜度O(n + m)

具體思路:

還是用兩個指標,從A B的第一個元素開始比較,如果A連結串列中的指向的元素比B小,A連結串列的指標就後移;如果A連結串列中指向的元素比B大,就直接把B的當前結點插入A連結串列當前節點的前面;如果相等,就直接保留A連結串列中的,將B連結串列的指標後移。注意,每次刪除B連結串列中的節點需要釋放空間。

//課本上的連結串列構造方式是第一個節點也就是連結串列名稱指向的節點是不存任何元素的
typedef struct node
{
	int data;
	struct node *next;
}LinkList;

//有序合併兩個遞增的連結串列
LinkList *Lmerge(LinkList *A, LinkList * B)
{
	LinkList *p, *q, *pre;//p指標指向的是A連結串列當前元素,q連結串列指向的是B連結串列當前元素,pre是q指標的前驅節點
	p = A -> next;//指向A連結串列中的第一個元素
	q = B -> next;//指向B連結串列中的第二個元素
	pre = A;//指向p前面
	free(B);//釋放B的空間
	B = q;
	while(p != NULL && q != NULL )
	{
		if(p -> data < q -> data)//A連結串列中的當前元素比B連結串列小
		{
			pre = p;
			p = p -> next;
		}
		else 
		{
			q = q ->  next;//這是q已經指向之前q的後面一個位置了
			if(p -> data > B -> data)//所以這邊不能寫成(p -> data > q -> data,這個地方課本上是錯的)//A連結串列中的當前元素比B連結串列大
			{
				B -> next = p; pre -> next = B; pre = pre -> next//把B連結串列的那個節點插到A連結串列當前元素前面
			}
			else free(B);
			B = q;
		}
	}
	if(q != NULL) pre -> next = q;
	return A;

}

3.已知長度為n的線性表A採用順序儲存結構,請寫一個時間複雜度為\(O(n)\)、空間複雜度為\(O(1)\)的演算法,該演算法可刪除線性表中所有值為\(item\)的資料元素。

具體思路:

因為是順序儲存,直接按照陣列下標查詢,如果值為\(item\),則刪除。

刪除操作:把陣列後面的元素向前移動一個位置即可

時間複雜度:最壞的情況下為\(O(n)\), 其中n為順序表長,也就是\(L -> last + 1\)

程式碼:

//基於以下儲存結構的順序表
typedef struct
{
	int data[maxlen];
	int last;
}Sequenlist;

Sequenlist *delete_item(Sequenlist *L, int item)
{
    for(int i = 0; i <= L -> last;i ++)
	{
		if(L -> data[i] == item)
		{
			for(int j = i + 1; j <= L -> last; j ++ ) L -> data[j] = L-> data[j + 1];
			L -> last --; 
			i --;//這一步操作是因為你已經把陣列後面的元素前移一個位置,那麼第i個位置就是以前的第i + 1個位置的數,沒有判斷過
		}
	}
}

4.設計一個演算法,通過一趟遍歷確定長度為n的單鏈表中最大的結點,返回該結點的資料域。

資料域:指的就是\(data\)

思路:

遍歷整個單鏈表,假設最大的資料域是頭節點指向的下一個結點的資料域,然後遍歷單鏈表,如果找到一個比該資料域還大的就更新。

程式碼:

//基於以下的儲存方式
typedef struct node
{
	int data;
	struct node *next;
}LinkList;

int findmax(LinkList *L)
{
	int maxn = L -> next -> data;
	L = L -> next;//L 指向的第一個結點是空的結點
	while(L -> next != NULL)
	{
		if(L -> data > maxn) maxn = L -> data;
		L = L -> next;
	}
	return maxn;
}

5.設計一個演算法將一個帶頭結點的單鏈表A分解為兩個具有相同結構的連結串列B和C,其中B表的結點為A表中值小於零的結點,而C表的結點為A表中大於零的結點(連結串列A中的元素為非零整數,要求B、C表利用A表的結點)。

思路:

單純的遍歷A連結串列,然後資料域為負數的插到B連結串列中,資料域為正數的插到C連結串列中。

程式碼:

//基於以下的儲存方式
typedef struct node
{
	int data;
	struct node *next;
}LinkList;

void divide(LinkList *A, LinkList *B, LinkList *C)
{
	LinkList *p;
	p = (LinkList *)malloc(sizeof (LinkList));		
	p = A -> next;
	LinkList *r;	
	r = (LinkList *)malloc(sizeof (LinkList));	
	while(p != NULL)
	{
		r = p -> next; //暫存p的後繼,因為在拆分連結串列的時候p會發現變化
		if(p -> data > 0)//插到C連結串列中
		{
			p -> next = C -> next;
			C -> next = p;
		}
		else//插到B連結串列中
		{
			p -> next = B -> next;
			B -> next = p;
		}
		p = r;
	}
}

6.設計一個演算法,將連結串列中所有結點的連結方向“原地”逆轉,即要求僅利用原表的儲存空間,換句話說,要求演算法的空間複雜度為\(O(1)\)

思路:

逆轉我們能想到:單鏈表頭插法,插完後元素剛好與插入順序相反(其實根本想不到,看題解才想到的(QwQ.jpg)

所以這題我們只需要對原連結串列中的結點按照順序進行一次頭插法就能逆轉連結串列。

程式碼:

LinkList *reverse(LinkList *L)
{
	LinkList *p;
	p = (LinkList *)malloc(sizeof (LinkList));
	p = L -> next;
	LinkList *q;
	q = (LinkList *)malloc(sizeof (LinkList));
	L -> next = NULL;
	while(p != NULL)
	{
		q = p -> next;
		p -> next = L -> next;
		L -> next = p;
		p = q;
	}
	return L;
}

7.已知兩個連結串列A和B分別表示兩個集合,其元素遞增排列。請設計一個演算法,用於求出A與B的交集,並存放在A連結串列中。

思路:

建立一個新的連結串列用來存交集,題目要保證,所有的結點都要是A連結串列的結點,再建立兩個指標\(pa\) \(pb\)遍歷\(A\)\(B\)連結串列,由於連結串列是遞增的,所以如果資料域相同,就把\(pa\)指向的結點給C連結串列。如果\(pa\)的資料域比\(pb\)的資料域小,\(pa\)指標就後移。如果\(pa\)的資料域比\(pb\)的資料域大,\(pb\)指標就後移,並釋放\(B\)連結串列中的結點的空間。當某一個連結串列遍歷完畢,交集也就找完了,釋放掉剩餘結點的空間。

程式碼:

//基於以下的儲存模式
typedef struct node
{
	int data;
	struct node *next;
}LinkList;

LinkList *merge(LinkList *A, LinkList *B, LinkList *C)
{
	LinkList *pa, *pb, *pc, *u;
	pa = A -> next;
	pb = B -> next;
	C = pc = A;
	while(pa != NULL && pb != NULL)
	{
		if(pa -> data == pb -> data)
		{
			pc -> next = pa, pc = pa, pa = pa -> next;	
			u = pb; pb = pb -> next; free(u);
		}
		else if(pa -> data < pb -> data)
		{
			u = pa;
			pa = pa -> next;
			free(u);
		}
		else
		{
			u = pb;
			pb = pb -> next;
			free(u);
		}
	}
	while(pa)
	{
		u = pa;
		pa = pa -> next;
		free(u);
	}
	while(pb)
	{
		u = pb;
		pb = pb -> next;
		free(u);
	}
	pc -> next = NULL;
	free(pb);
	return C;
}

8.已知兩個連結串列\(A\)\(B\)分別表示兩個集合,其元素遞增排列。請設計演算法求出兩個集合A和B 的差集(即僅由在A中出現而不在B中出現的元素所構成的集合),並以同樣的形式儲存,同時返回該集合的元素的個數。

思路:

用兩個工作指標分別遍歷A和B連結串列,並記錄下A連結串列的前驅節點(為了刪除用的),如果當前A連結串列中的元素等於B連結串列中的元素就刪除A中的結點,如果A連結串列中的元素小於B連結串列中的元素,則B連結串列一定不會出現和A連結串列當前結點相同的結點(因為連結串列是遞增的),差集個數 \(++\),如果A連結串列的元素大於B連結串列的元素,B連結串列的工作指標就後移。最後A連結串列如果還有剩餘,剩餘的也都是差集。

程式碼:

//基於以下的儲存模式
typedef struct node
{
	int data;
	struct node *next;
}LinkList;

void QwQ(LinkList *A, LinkList *B, int *n)
{
	LinkList *pa, *pb, *pre;
	pa = A -> next;
	pb = B -> next;
	while(pa && pb)
	{
		if(pa -> data < pb -> data)//如果A集合中的元素小於B集合中的元素,A集合的元素不會在B中出現(因為連結串列是遞增的)
		{
			n ++;
			pre = pa;
			pa = pa -> next;
		}
		else if(pa -> data > pb -> data)//如果A集合中的元素大於B集合中的元素B的工作指標後移
			pb = pb -> next;
		else//如果A集合和B集合中的元素相同刪除A集合的元素
		{
			pre -> next = pa -> next;
			LinkList *u = pa;
			pa = pa -> next;
			free(u);
		}
	}
	while(pa)//A連結串列可能沒有遍歷完
	{
		pa = pa -> next;
		n ++;
	}
}

9.設計一個演算法,刪除遞增有序連結串列中值大於\(mink\)且小於\(maxk\)\(mink\)\(maxk\)是給定的兩個引數,其值可以和表中的元素相同,也可也不同)的所有元素。

思路:

遍歷連結串列,找到第一個大於\(mink\)的和最後一個小於\(maxk\)的,然後一一刪除就行。

程式碼:

//基於以下的儲存模式
typedef struct node
{
	int data;
	struct node *next;
}LinkList;

void delete(LinkList *L, int mink, int maxk)
{
	LinkList *p = L -> next;
	LinkList *pre;//最後一個小於等於mink的結點
	while(p && p -> data <= mink)
	{
		pre = p;
		p = p -> next;
	}
	while(p && p -> data < maxk)
		p = p -> next;//第一個大於maxk的結點
	q = pre -> next;//需要刪除的第一個結點
	while(q != p)
	{
		LinkList *u = q -> next;
		free(q);
		q = u;
	}
}

10二叉樹的基本演算法:

1.求葉子結點數目

//基於以下儲存結構
typedef struct node
{
	int data;
	struct node *lchild, *rchild;
}Bitree;

//1.求葉子結點數目
int countleaf(Bitree *bt)
{
	if(bt == NULL) return 0;
	else if(bt -> lchild == NULL && bt -> rchild == NULL) return 1;
	else return countleaf(bt -> lchild) + countleaf(bt -> rchild);
}

2.求結點數目

//基於以下儲存結構
typedef struct node
{
	int data;
	struct node *lchild, *rchild;
}Bitree;

//2.求結點數目
int count(Bitree *bt)
{
	if(bt == NULL) return 0;
	else return 1 + count(bt -> lchild) + count(bt -> rchild);
}

3.交換左右子樹

//基於以下儲存結構
typedef struct node
{
	int data;
	struct node *lchild, *rchild;
}Bitree;

//3.交換所有結點的左右子樹
void swap(Bitree *T)
{
	Bitree *temp;
	if(T == NULL) return;
	else
	{
		temp = T -> lchild;
		T -> lchild = T -> rchild;
		T -> rchild = temp;
		swap(T -> lchild);
		swap(T -> rchild);
	}
}

4.求一棵二叉樹的深度
//基於以下儲存結構
typedef struct node
{
	int data;
	struct node *lchild, *rchild;
}Bitree;


//求一棵二叉樹的深度
int treedepth(Bitree * bt)
{
	if(bt == NULL) return 0;
	else return (max(treedepth(bt -> lchild), treedepth (bt -> rchild)) + 1);
}

11.以二叉連結串列作為二叉樹的儲存結構,判斷兩棵樹是否相等

思路:遞迴判斷兩棵樹的所有結點是否一樣。不一樣的情況有:

(1)兩個結點只有一個是空結點

(2)兩個結點的資料域不一樣

程式碼:

//基於以下儲存結構
typedef struct node
{
	int data;
	struct node *lchild, *rchild;
}Bitree;

int is_same(Bitree T1, Bitree T2)
{
	if(T1 == NULL && T2 == NULL) return 1;
	if((T1 == NULL && T2 != NULL) || (T2 == NULL && T1 != NULL)) return 0;
	if(T1 -> data != T2 -> data) return 0;
	return is_same(T1 -> lchild, T2 -> lchild) && is_same(T1 -> rchild, T2 -> rchild);
}

12.計算二叉樹的最大寬度(二叉樹的最大寬度是指二叉樹所有層中結點個數的最大值)

思路:利用佇列進行寬度優先遍歷

定義佇列:一開始隊頭和隊尾都是指向1,當隊頭大於隊尾,\(BFS\)結束,

每次遍歷完每一層後更新最大值

小插曲:本來想畫圖幫忙理解的,\(sai\)真難用(我是弱智.jpg)

程式碼:

//基於以下儲存結構
typedef struct node
{
	int data;
	struct node *lchild, *rchild;
}Bitree;

int get_width(Bitree *T)
{
	if(T == NULL) return 0;
	Bitree Q[100010];//開到足夠大就行,我習慣這麼開
	int front = 1, rear = 1, last = 1, temp = 0, maxn = 0;
	Q[1] = T;
	while(front <= last) //BFS
	{
		p = Q[front ++];
		temp ++;
		if(p -> lchild != NULL) Q[ ++ rear] = p -> lchild;
		if(p -> rchild != NULL) Q[ ++ rear] = p -> rchild;
		if(front > last)//一層遍歷結束
		{
			last = rear;
			if(temp > maxn) maxn = temp;
			temp = 0;
		}
	}
	return maxn;
} 

13.用層次順序遍歷二叉樹的方法,統計樹中具有度為1的結點數目。

思路:本題直接\(BFS\)求,度為一的結點顯然兩種情況,左右孩子只有一個不是\(NULL\)

程式碼:

//基於以下儲存結構
typedef struct node
{
	int data;
	struct node *lchild, *rchild;
}Bitree;

int get(Bitree *T)
{
	int num = 0;
	if(T == NULL) return 0;
	queue<Bitree>q;//直接上C++了,自己C語言手寫佇列也行。
	q.push(T);
	while(!q.empty())
	{
		auto it = q.top();
		q.pop();
		if((it -> lchild == NULL && it -> rchild != NULL) || (it -> rchild == NULL && it -> lchild != NULL)) num ++;
		if(it -> child != NULL) q.push(it -> lchild);
		if(it -> rchild != NULL) q.push(it.rchild);
	}
	return num;
}

14.求任意二叉樹中第一條最長的路徑長度,並輸出此路徑上各結點的值

思路:用棧模擬全過程,先遍歷完左子樹,然後再依次遍歷右子樹求最大長度(又是我這個弱智想不出來的

程式碼:

//基於以下儲存結構
typedef struct node
{
	int data;
	struct node *lchild, *rchild;
}Bitree;


void getlongestpath(Bitree T)
{
	int tag[100010];//記錄右孩子是否遍歷過
	Bitree p = T, l[100010], s[100010];//陣列l和s存的是棧,而l裡面存的是最長的路徑
	int top = 0, length = 0;//top棧頂, length是最長路徑的長度
	while(p || top > 0)
	{
		while(p != NULL)//先把左子樹遍歷完
		{
			s[++ top] = p;
			tag[top] = 0;
			p = p -> lchild;
		}
		if(tag[top] == 1)//如果右子樹已經遍歷完
		{
			if(!s[top] -> lchild && !s[top] -> rchild)//如果是葉子結點就能求最大長度
				if(top > length)
				{
					for(int i = 1; i <= top; i ++ ) 
						l[i] = s[i];
					length = top;
				}
			top --;//出棧
		}
		else if(top > 0)//遍歷當前棧頂的右子樹
		{
			tag[top] = 1;
			p = s[top] -> rchild;
		} 
	}
	for(int i = length; i >= 1; i -- ) printf("%d ", l[i] -> data);//輸出答案
	printf("\n");
}

15.輸出二叉樹中從每個葉子結點到根結點的路徑。

思路:對整棵二叉樹進行深度優先遍歷,並存儲路徑,要記得恢復現場。遍歷到葉子結點輸出路徑。

程式碼:

//基於以下儲存結構
typedef struct node
{
	int data;
	struct node *lchild, *rchild;
}Bitree;

void dfs(Bitree T, int path[], int length)
{
	if(T == NULL) return;
	if(T -> lchild == NULL && T -> rchild == NULL)
	{
		printf("%d ", T -> data);
		for(int i = length - 1; i >= 0; i  -- ) printf("%d ", path[i]);
		printf("\n");
	}
	else
	{
		path[length] = T -> data;
		length ++;
		dfs(T -> lchild, path. length);
		dfs(T -> rchild, path, length);
		length --;
	}
}

二.解答題+分析題(70分)

圖論基礎知識:給你一張有向圖,畫出其鄰接矩陣、鄰接表、逆鄰接表,會畫出從某個點開始的\(dfs\) \(bfs\)的搜尋圖(看清題目)。

一.最小生成樹(MST)

定義:

給你n個結點,和m條已知長度的無向邊,讓你構造一棵樹,這棵樹要滿足兩個條件:

①這棵樹連線所有n個結點
②這棵樹總邊權之和最小

(1).普利姆演算法(\(Prim\)

演算法思想:

假設你有一個集合S,一開始集合S裡面只有一個結點\(u0\)\(u0\)是你構造MST的最初結點),然後你從剩餘\(n -1\)個點中尋找到集合S最小的那個點(到集合的意思是:集合外部的點到集合內部任意一個點有邊),把這個點加入集合,重複以上操作,直至所有的n個結點都加入集合S,則MST構造完成。

演算法特點:不斷加點構造

(2).克魯斯卡爾演算法(\(Kruskal\))

演算法思想:首先對\(m\)條無向邊的邊權進行排序,然後依次遍歷所有的邊,把邊的兩個結點依次放到一個集合中,如果發現這兩個結點在一個集合中,就跳過,遍歷完所有的邊為止。

演算法特點:不斷加邊

二.最短路演算法 (大概率考第一個)

1.單源最短路:\(Dijkstra\)演算法

演算法思想:(基於貪心思想)

一開始把源點到源點的距離初始化為0,其餘點到源點的距離初始化為無窮大。我們定義一個集合S,集合S內部放的是已經確定最短路徑的點,一開始集合內部只有源點。我們經過\(n - 1\)迭代,尋找集合外部的點到集合內部最近的點\(t\),把\(t\)加到集合S中,用\(t\)更新其他點到集合的距離。

2.多源匯最短路:\(Floyd\)演算法

演算法思想:(基於動態規劃思想)

我們令\(d[k,i,j]\)表示第\(i\)個結點只經過\(1\)~\(k\),到達\(j\)的最短距離

狀態轉移方程:\(d[k, i, j] = d[k - 1, i, k] + d[k - 1, k, j]\)

演算法優化:\(d[i, j] = d[i, k] + d[k, j]\)

三.樹的遍歷:

(1).基礎的會\(dfs\)\(bfs\)

(2).會樹的三種遍歷方式(\(DLR,LDR,LRD\)),會根據遍歷結果畫出樹。

四.樹與堆的建立

(1).二叉排序樹的建立:\(lchild -> data < father -> data < rchild -> data\) 滿足中序遍歷是一個遞增的序列

(2).大根堆的建立:先把所有的元素畫成一棵二叉樹,然後從根結點開始,先比較它的左右孩子,拿大的和根結點比較,大的成為根節點,小的成為左右孩子,依次遞迴處理完,大根堆建立完成。

(3).小根堆的建立:類似於大根堆的建立。

五.求有向圖的拓撲序列:

依次刪除入度為0的點,答案不唯一。

六.哈夫曼樹

給你幾個帶權值的結點,建立哈夫曼樹:依次取當前權值最小和次小的兩個結點合併產生新的結點

編碼:左\(0\)\(1\)

七:排序

精通 直接插入排序、希爾排序、直接選擇排序、氣泡排序、快速排序、歸併排序。

八.查詢:

主要是二分查詢,二分(眾所周知,world final algorithm),一定要在遞增的順序表,時間複雜度\(log^n\) (計算機科學裡面的\(log\)都是以2為底的)。

九 散列表(書本267頁例13.10)

會畫出散列表,會求平均查詢長度

十 簡單的資料結構

會棧、佇列、會求時間複雜度。