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; 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 != 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).大根堆的建立:先把所有的結點畫成一棵完全二叉樹,然后從\(n/2\)結點開始,先比較它的左右孩子,拿大的孩子和此結點比較,數值大的成為根節點,小的成為其孩子,再從\(n/2−1\)結點繼續類似操作,依次遞歸處理到頭節點,大根堆建立完成。。

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

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

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

六.哈夫曼樹

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

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

七:排序

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

八.查找:

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

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

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

十 簡單的數據結構

會棧、隊列、會求時間復雜度。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM