資料結構與演演算法3 -- 線性表練習題
前言
前面已經講了資料在邏輯上有集合、線性、樹形、圖形這些結構,並且也已經學習了線性結構是什麼?
這篇文章就是來做一些跟線性表相關的練習題。
下面是這些練習題都需要使用到的相關定義及函式
// 定義節點
typedef struct _Node {
int data;
struct _Node *next;
} Node;
// 連結串列
typedef Node* LinkList;
// 初始化連結串列
LinkList initLinkList(void) {
LinkList ll = malloc(sizeof(Node)); // 頭節點
ll->data = 0 ;
ll->next = NULL;
// 迴圈新增節點
int data = 0;
Node *p = ll; // p指向最後一個節點
printf("請輸入一個遞增連結串列:(非遞增時自動退出輸入)\n");
scanf("%d",&data);
while (data >= p->data) {
// 建立節點
Node *node = malloc(sizeof(Node));
node->data = data;
node->next = NULL ;
// 連結到連結串列
p->next = node;
p = node;
// 輸入下一個數
scanf("%d",&data);
}
return ll;
}
void foreachLinkList(LinkList ll) {
Node *p = ll->next; // ll指向頭節點,ll->next指向真正的第一個節點
while (p) {
printf("%-5d",p->data);
p = p->next;
}
printf ("\n");
}
複製程式碼
練習題
題目一
將2個遞增
的有序
連結串列合併為一個連結串列的有序
連結串列,要求:
- 結果連結串列仍然使⽤用兩個連結串列的儲存空間,不另外佔⽤用其他的儲存空間。
- 表中不允許有重複的資料。
題目分析:
不能使用其他儲存空間,意味著不能開闢除了這兩個連結串列以外的其他堆空間,因此我們可以將連結串列2中的節點給按大小順序插入到連結串列1中。
多餘節點仍然可以先保留在連結串列2中,需要釋放時可以直接遍歷連結串列2,釋放全部節點。
舉例:
連結串列1: 1 3 5 7 8 10 12
連結串列2: 2 4 6 8 10 12
// 合併後的結果是
連結串列3: 1 2 3 4 5 6 7 8 10 12
複製程式碼
程式碼:
// 合併遞增連結串列
LinkList mergeLinkLists(LinkList l1,LinkList l2) {
LinkList l3 = NULL;
Node *p = NULL;
Node *node = NULL;
Node *headNode = l1;
while (l1->next && l2->next) {
if (l1->next->data <= l2->next->data) {
// 需要移動的node
node = l1->next;
l1->next = node->next; // 刪除這個node
// 相等時特殊處理
if (node->data == l2->next->data) {
l2 = l2->next;
}
}
else {
node = l2->next;
l2->next = node->next;
}
node->next = NULL; // 斬斷聯絡
if (l3 == NULL) {
l3 = node;
p = node;
}
else {
p->next = node;
p = node;
}
}
// 有一個連結串列已經沒有元素了,則將剩下的直接拼到p的後面就可以了,同時要記得把l1和l2後面斬斷
node = l1->next ? l1->next : l2->next;
p->next = node;
l1->next = NULL;
l2->next = NULL;
headNode->next = l3;
l3 = headNode;
return l3;
}
// 測試程式碼
int main() {
LinkList l1 = initLinkList();
printf("l1遍歷--->");
foreachLinkList(l1);
LinkList l2 = initLinkList();
printf("l2遍歷--->");
foreachLinkList(l2);
// 合併連結串列
LinkList l3 = mergeLinkLists(l1,l2);
printf("l3遍歷--->");
foreachLinkList(l3);
while (l2) {
Node *fNode = l2;
l2 = l2->next;
fNode->data = 0;
fNode->next = NULL;
free(fNode);
}
return 0;
}
複製程式碼
題目二
已知兩個連結串列A和B分別表示兩個集合。其元素遞增排列列。 設計一個演演算法,⽤用於求出A與B的交集,並儲存在A連結串列中; 例如: La = {2,4,6,8}; Lb = {4,8,10}; Lc = {4,8}
題目分析:
求交集和和上一個題目合併有序連結串列剛好相反。
程式碼:
// 求兩個連結串列的交集,和連結串列合併剛好相反
LinkList intersection(LinkList l1,LinkList l2) {
LinkList l3 = NULL;
Node *p = NULL;
while (l1->next && l2->next) {
Node *node = NULL;
// 相等
if (l1->next->data == l2->next->data) {
node = l1->next;
// 往後移一步
l1->next = node->next;
l2->next = l2->next->next;
}
else {
Node *cNode = (l1->next->data < l2->next->data) ? l1 : l2;
cNode->next = cNode->next->next;
}
if (node) {
node->next = NULL;
if (l3 == NULL) {
l3 = p = node;
}
else {
p->next = node;
p = p->next;
}
}
}
return l3;
}
// 測試程式碼
int main() {
// 建立連結串列
LinkList l1 = initLinkList();
printf("l1遍歷--->");
foreachLinkList(l1);
LinkList l2 = initLinkList();
printf("l2遍歷--->");
foreachLinkList(l2);
// 求交集
LinkList l3 = intersection(l1,l2);
printf("l3遍歷--->");
Node *p = l3;
while (p) {
printf("%-5d",p->data);
p = p->next;
}
printf("\n");
return 0;
}
複製程式碼
題目3
設計⼀個演演算法,將連結串列中所有節點的連結方向"原地旋轉",即要求僅利用原表的儲存空間。換句話說,要求演演算法空間複雜度為O(1); 例如:L={0,2,10},逆轉後: L = {10,0};
題目分析:
我們可以定義3個指標變數(ps:別說什麼不能開闢空間,這裡指的是使用連結串列原本的空間,即不開闢額外的堆空間,定義幾個指標還是可以的),front、now、next,分別代表前一個節點,當前的這個節點,下一個節點。
然後遍歷連結串列,將now->next = front
,再接著將front、now和next依次往後移一個即可實現。
程式碼如下:
// 反轉連結串列
void InvertLinkList(LinkList *ll) {
Node *front = NULL;
Node *now = (*ll)->next;
Node *next = now ? now->next : NULL;
while (now) {
now->next = front;
// 往後移
front = now;
now = next;
next = next ? next->next : NULL;
}
(*ll)->next = front;
}
// 測試程式碼
int main() {
// 原連結串列
LinkList ll = initLinkList();
printf("原連結串列:\n");
foreachLinkList(ll);
// 反轉連結串列
InvertLinkList(&ll);
printf("反連結串列:\n");
foreachLinkList(ll);
return 0;
}
複製程式碼
題目四
設計一個演演算法,刪除遞增有序連結串列中值大於等於mink且小於等於maxk(mink,maxk是給定的兩個引數,其值可以和表中的元素相同,也可以不不同)的所有元素。
題目分析:
由於此題目讓刪除的是一個區間,因此我們只需要找到連結串列中要刪除的第一個節點和要刪除的最後一個節點即可。注意,有可能區間最大值比連結串列最大值大,或者區間最小值比連結串列最小值小。
程式碼:
void deleteNode(LinkList *ll,int mink,int maxk) {
Node *p = *ll; // 要刪除的第一個節點的前一個
Node *q = NULL; // 要刪除的最後一個節點
Node *r = (*ll)->next;
while (r) {
if (r->data < mink) {
p = r;
}
if (!q && r->next && r->next->data > maxk) {
q = r;
}
// 如果q還沒找到,並且連結串列已經迴圈完了
if (!q && r->next == NULL) {
q = r;
}
r = r->next;
}
// 儲存要釋放的連結串列
LinkList fl = p->next;
// 刪除連結串列段
p->next = q->next;
// 斷了fl和後面沒有刪除的連結串列之間的聯絡
q->next = NULL;
// 釋放連結串列
freeLinkList(&fl);
}
// 測試程式碼
int main() {
LinkList ll = initLinkList();
printf("原連結串列:\n");
foreachLinkList(ll);
// 刪除某一段資料
int min = 0,max = 0;
printf("請輸入min和max,注意min<=max\n");
scanf("%d%d",&min,&max);
deleteNode(&ll,min,max);
printf("刪後表:\n");
foreachLinkList(ll);
return 0;
}
複製程式碼
題目五
設將n(n>1)個整數存放到一維陣列R中,試設計一個在時間和空間兩方面都儘可能高效的演演算法;將R中儲存的序列列迴圈左移p個位置(0<p<n)個位置
即將R中的資料由(x0,x1,......,xn-1)變換為(xp,xp+1,...,xn-1,x0,xp-1)。
例如: pre[10] = {0,1,3,5,7,9},n = 10,p = 3; pre[10] = {3,9,2}
題目分析:
這裡的思路是將需要偏移的資料儲存到另一個陣列中,當i >= p
時,將i位置的資料賦值到i-p
的位置,當i >= n-p
時,再將另一個陣列儲存的前p個資料賦值到原陣列中。
空間複雜度:。
時間複雜度:。
int main() {
int n,p;
printf("請輸入n和p的值,注意0<p<n\n");
scanf("%d%d",&n,&p);
int r[n];
for (int i = 0; i < n; i++) {
r[i] = n - i;
printf("%-5d",r[i]);
}
printf("\n");
// 向左偏移
int a[p];
for (int i = 0; i < n; i++) {
if (i < p) {
a[i] = r[i];
}
if (i >= p) {
r[i - p] = r[i];
}
if (i >= n - p) {
r[i] = a[i + p - n];
}
}
// 列印陣列
for (int i = 0; i < n; i++) {
printf("%-5d",r[i]);
}
printf("\n");
return 0;
}
複製程式碼
題目六
已知一個整數序列列A = (a0,a1,a2,...an-1),其中(0<= ai <=n),(0<= i<=n). 若存在ap1= ap2 = ...= apm = x,且m>n/2(0<=pk<n,1<=k<=m),則稱x 為 A的主元素。
例如:
A = (0,5),則5是主元素。
B = (0,7),則B中沒有主元素。
假設A中的n個元素儲存在一個一維陣列中,請設計一個儘可能高效的演演算法,找出陣列元素中的主元素,若存在主元素則輸出該元素,否則輸出-1。
題目分析:
所謂主元素,其實就是在陣列中重複次數超過陣列總元素數量的一半的元素。
程式碼:
int main() {
int n;
printf("請輸入陣列元素個數:\n");
scanf("%d",&n);
int a[n];
printf("請輸入陣列元素的值:\n");
for (int i = 0; i < n; i++) {
scanf("%d",&a[i]);
}
// 遍歷
for (int i = 0; i < n; i++) {
printf("%-5d",a[i]);
}
printf("\n\n");
// 求主要元素
int b[n];
for (int i = 0; i < n; i++) {
b[i] = 0; // 預設全部元素都設定為0
}
for (int i = 0; i < n; i++) {
b[a[i]]++;
}
int maxCount = 0;
int maxCountNum = 0;
for (int i = 0; i < n; i++) {
if (b[i] > maxCount) {
maxCount = b[i];
maxCountNum = i;
}
}
if (maxCount > n / 2) {
printf("主要元素是:%d\n",maxCountNum);
}
else {
printf("沒有主要元素:-1\n");
}
return 0;
}
複製程式碼
題目七
用單連結串列儲存m個整數,結點的結構為(data,link),且|data|<=n(n為正整數)。 現在要去設計一個時間複雜度儘可能高效的演演算法。
對於連結串列中的data絕對值相等的節點,僅保留留第一次出現的節點,而 刪除其餘絕對值相等的節點。
例如:連結串列A = {21,-15,15,-7,15},刪除後的連結串列A={21,-7};
題目分析:
注意:時間複雜度儘可能高效,沒說空間複雜度?。
我們可以建立一個陣列,用來儲存每一個絕對值出現的次數,如果已經出現過了,則就刪除這個節點。
程式碼如下:
// 獲取絕對值
int absoluteValue(int a) {
return (a >= 0) ? a : -a;
}
int main() {
int n = 20;
LinkList ll = initLinkList(n);
foreachLinkList(ll);
// 初始化計數陣列
int a[n];
for (int i = 0; i < n; i++) {
a[i] = 0;
}
// 遍歷連結串列
Node *p = ll->next;
Node *q = ll;
while (p) {
if (a[absoluteValue(p->data)] > 0) {
q->next = p->next;
p->data = 0;
p->next = NULL;
free(p);
p = q->next;
}
else {
a[absoluteValue(p->data)]++;
q = p;
p = p->next;
}
}
foreachLinkList(ll);
return 0;
}
複製程式碼
總結
額,這裡不知道寫啥了,就練習了一下線性表唄。?
本文地址https://juejin.im/post/5eb3a484f265da7bf873da95