1 從數組到鏈表
1 從結構體延申至鏈表(linked list)
2 從概念上理解鏈表
鏈表的頭指針,保存了鏈表第一項的地址,即頭指針指向了鏈表第一項。
添加鏈表項
2 使用鏈表
#include <stdio.h>
#include <stdlib.h> /* 提供 malloc() 函數原型 */
#include <string.h>
char * s_gets(char * st, int n);
#define TSIZE 45
struct film {
char title[TSIZE];
int rating;
struct film * next; /* 指向鏈表的下一個結構體 */
};
int main(void)
{
struct film * head = NULL; /* 頭指針 */
struct film * tail = NULL; /* 尾指針 */
struct film *current = NULL;
char input[TSIZE];
/* 收集並存儲信息 */
puts("Enter first movie title:");
while (s_gets(input, TSIZE) != NULL && input[0] != '\0')
{
current = (struct film *)malloc(sizeof(struct film)); /* 爲新結構體申請內存 */
if (head == NULL) /* 鏈表的第一個結構體 */
head = current;
else
tail->next = current; /* 讓鏈表的尾指針記錄當前項的地址 */
current->next = NULL; /* 當前新增的鏈表項 *next 指向NULL */
strcpy(current->title, input);
puts("Enter your rating <0 - 10> :");
scanf("%d", ¤t->rating);
while (getchar() != '\n') /* 清除緩衝區 */
continue;
puts("Enter next movie title (empty line to quit) :");
tail = current; /* 當前項設置爲鏈表的尾指針 */
}
/* 顯示電影鏈表 */
if (head == NULL)
puts("No data entered!");
else
puts("Here is the movie list:");
current = head; /* 除釋放內存外,要保持 head 頭指針不能變,否則找不到鏈表 */
while (current != NULL)
{
printf("Movie: %s Rating: %d\n", current->title, current->rating);
current = current->next;
}
/* 完成任務,釋放鏈表所有結構體的內存 */
puts("\nReady to free memory...");
current = head;
while (head != NULL) /* 從頭指針開始,逐項往下釋放內存 */
{
current = head;
head = head->next;
free(current);
}
puts("All done!");
return 0;
}
char * s_gets(char * st, int n)
{
char * find;
char * ret_val;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n');
if (find)
*find = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
程序執行結果
鏈表的狀態:
程序分析:
1 顯示鏈表
爲什麼不直接使用 head 指針遍歷?
2 創建鏈表
3 釋放鏈表
3 抽象數據類型
1 建立抽象
2 建立接口
描述鏈表的接口
3 使用接口
list.h
/* list.h -- 簡單鏈表類型的頭文件 */
#ifndef __LIST_H
#define __LIST_H
#include <stdbool.h>
/* 特定程序的說明 */
#define TSIZE 45 /* 存儲數組電影名的數組大小 */
struct film {
char title[TSIZE];
int rating;
};
/* 一般類型定義 */
typedef struct film Item; /* 節點數據域 */
struct node {
Item item;
struct node * next; /* 節點結構體 */
};
typedef struct node Node;
typedef Node * List;
/* 函數原型 */
/* 操作: 初始化一個鏈表 */
/* 前提條件: plist 指向一個鏈表 */
/* 後置條件: 該鏈表初始化爲空 */
void InitializeList(List * plist);
/* 操作: 確定鏈表是否爲空定義,plist 指向一個已初始化的鏈表 */
/* 後置條件: 如果鏈表爲空,該函數返回 true;否則返回 false */
bool ListIsEmpty(const List * plist);
/* 操作: 確定鏈表是否已滿,plist 指向一個已初始化的鏈表 */
/* 後置條件: 如果鏈表已滿,該函數返回 true;否則返回 false */
bool ListIsFull(const List * plist);
/* 操作: 確定鏈表中的項數,plist 指向一個已初始化的鏈表 */
/* 後置條件: 該函數返回鏈表的項數 */
unsigned int ListItemCount(const List * plist);
/* 操作: 在鏈表的末尾添加項 */
/* 前提條件: Item 是一個待添加至鏈表的項,plist 指向一個已初始化的鏈表 */
/* 後置條件: 如果可以,該函數在鏈表末尾添加一個項,且返回 true;否則返回 false */
bool AddItem(Item item, List * plist);
/* 操作: 把函數作用與鏈表的每一個項 */
/* plist 指向一個已初始化的鏈表 */
/* pfun 指向一個函數,該函數接受一個 Item 類型的參數,且無返回值 */
/* 後置條件: pfun 指向的函數作用於鏈表中的每一項一次 */
void Traverse(const List * plist, void(*pfun)(Item item));
/* 操作: 釋放已分配的內存(如果有的話) */
/* plist 指向一個已初始化的鏈表 */
/* 後置條件: 釋放了爲鏈表分配的所有內存,鏈表設置爲空 */
void EmptyTheList(List * plist);
#endif /* __LIST_H */
main.c
#include <stdio.h>
#include <stdlib.h> /* 提供 exit() 函數原型 */
#include <string.h>
#include "list.h"
void showmovies(Item item);
char * s_gets(char * st, int n);
int main(void)
{
List movies;
Item temp;
/* 初始化鏈表 */
InitializeList(&movies);
if (ListIsFull(&movies))
{
fprintf(stderr, "No memory avaliable, Bye!\n");
exit(EXIT_FAILURE);
}
/* 獲取用戶輸入並存儲 */
puts("Enter first movie title:");
while (s_gets(temp.title, TSIZE) != NULL && temp.title[0] != '\0')
{
puts("Enter your rating <0 - 10> :");
scanf("%d", &temp.rating); /* 讀取輸入完成,清空緩衝區 */
while (getchar() != '\n')
continue;
if (AddItem(temp, &movies) != true)
{
fprintf(stderr, "Problem allocating memory!\n");
break;
}
if (ListIsFull(&movies))
{
puts("The list is now full");
break;
}
puts("Enter next movie title (empty line to quit) :");
}
/* 顯示 */
if (ListIsEmpty(&movies))
puts("No data entered.");
else
{
puts("Here is the movie list:");
Traverse(&movies, showmovies); /* 調用回調函數打印鏈表 */
}
printf("You entered %d movies.\n", ListItemCount(&movies));
/* 釋放內存 */
EmptyTheList(&movies);
puts("All done.");
return 0;
}
void showmovies(Item item)
{
printf("Movie: %s Rating: %d\n", item.title, item.rating);
}
char * s_gets(char * st, int n)
{
char * find;
char * ret_val;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n');
if (find)
*find = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
4 實現接口
list.c
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
static void CopyToNode(Item item, Node * node);
void InitializeList(List * plist)
{
*plist = NULL;
}
bool ListIsEmpty(const List * plist)
{
if (*plist == NULL)
return true;
else
return false;
}
bool ListIsFull(const List * plist)
{
Node * pt;
bool full;
pt = (Node *)malloc(sizeof(Node));
if (pt == NULL)
full = true;
else
full = false;
free(pt);
return full;
}
unsigned int ListItemCount(const List * plist)
{
unsigned int count = 0;
Node * pnode = *plist; /* 設置鏈表的開始 */
while (pnode != NULL)
{
count++;
pnode = pnode->next; /* 設置下一個節點 */
}
return count;
}
bool AddItem(Item item, List * plist)
{
Node * pnew;
Node * scan = *plist;
pnew = (Node *)malloc(sizeof(Node)); /* 爲新節點分配內存 */
if (pnew == NULL)
return false;
CopyToNode(item, pnew); /* 新節點拷貝至新內存 */
pnew->next = NULL;
if (scan == NULL) /* 空鏈表,所以把 */
*plist = pnew; /* pnew 放在鏈表的開頭 */
else
{
while (scan->next != NULL) /* 找到鏈表的尾節點 */
scan = scan->next;
scan->next = pnew;
}
return true;
}
void Traverse(const List * plist, void(*pfun)(Item item))
{
Node * pnode = *plist; /* 其實是找到頭指針 */
while (pnode != NULL)
{
pfun(pnode->item); /* 把函數應用於鏈表中的項 */
pnode = pnode->next;
}
}
void EmptyTheList(List * plist)
{
Node * phead = *plist;
Node * pnode;
while (phead != NULL)
{
pnode = phead;
phead = phead->next;
free(pnode);
}
}
static void CopyToNode(Item item, Node * node)
{
node->item = item; /* 拷貝結構體 */
}
4 隊列
指針的關鍵點:頭指針和頭節點。使用頭節點,能方便鏈表的操作。
1 定義隊列抽象數據類型
2 定義接口
3 實現接口數據表示
隊列的尾部插入操作
bool EnQueue(Item item, Queue * pq)
{
Node * pnew; // 創建節點指針,malloc內存,然後將數據項拷貝至內存
if (QueueIsFull(pq)) // 隊列已滿
return false;
pnew = (Node *)malloc(sizeof(Node)); // 申請內存
if (pnew == NULL)
{
fprintf(stderr, "Unable to allocate memory!\n");
exit(EXIT_FAILURE);
}
CopyToNode(item, pnew); // 拷貝節點數據項至內存
pnew->next = NULL;
if (QueueIsEmpty(pq)) // 如果隊列爲空,則設爲隊列頭
pq->front = pnew;
else // 隊列不爲空,則添加至當前隊列尾的尾部
pq->rear->next = pnew;
pq->rear = pnew; // 重新設置隊列尾
pq->count++; // 隊列計數 + 1
return true;
}
隊列的頭部刪除操作
bool DeQueue(Item * pitem, Queue * pq)
{
Node * pt;
if (QueueIsEmpty(pq))
return false;
CopyToItem(pq->front, pitem); // 隊列頭部鏈表項,拷貝至
pt = pq->front; // 獲取要刪除的鏈表項
pq->front = pt->next; // 隊列頭指向下一個鏈表項
free(pt);
pq->count--;
if (pq->count == 0)
pq->rear = NULL;
return true;
}
隊列實現如下
queue.h
#ifndef __QUEUE_H
#define __QUEUE_H
#include <stdbool.h>
#define MAX_QUEUE 10 /* 隊列最大長度 */
typedef int Item;
struct node
{
Item item;
struct node *next;
};
typedef struct node Node;
struct queue
{
Node * front; // 指向隊列頭的指針
Node * rear; // 指向隊列尾的指針
int count; // 隊列中的項數
};
typedef struct queue Queue;
// 初始化隊列
void InitializeQueue(Queue * pq);
// 檢查隊列是否已滿
bool QueueIsFull(const Queue * pq);
// 檢查隊列是否爲空
bool QueueIsEmpty(const Queue * pq);
// 確定隊列中的項數
int QueueItemCount(const Queue * pq);
// 在隊列末尾插入項
bool EnQueue(Item item, Queue * pq);
// 從隊列開頭刪除項
bool DeQueue(Item * pitem, Queue * pq);
// 清空隊列
void EmptyTheQueue(Queue * pq);
#endif /* __QUEUE_H */
queue.c
#include <stdio.h>
#include <stdlib.h>
#include "queue.h"
static void CopyToNode(Item item, Node * pnode);
static void CopyToItem(Node * pnode, Item * pitem);
void InitializeQueue(Queue * pq)
{
pq->front = NULL;
pq->rear = NULL;
pq->count = 0;
}
bool QueueIsFull(const Queue * pq)
{
return pq->count == MAX_QUEUE;
}
bool QueueIsEmpty(const Queue * pq)
{
return pq->count == 0;
}
int QueueItemCount(const Queue * pq)
{
return pq->count;
}
bool EnQueue(Item item, Queue * pq)
{
Node * pnew; // 創建節點指針,malloc內存,然後將數據項拷貝至內存
if (QueueIsFull(pq)) // 隊列已滿
return false;
pnew = (Node *)malloc(sizeof(Node)); // 申請內存
if (pnew == NULL)
{
fprintf(stderr, "Unable to allocate memory!\n");
exit(EXIT_FAILURE);
}
CopyToNode(item, pnew); // 拷貝節點數據項至內存
pnew->next = NULL;
if (QueueIsEmpty(pq)) // 如果隊列爲空,則設爲隊列頭
pq->front = pnew;
else // 隊列不爲空,則添加至當前隊列尾的尾部
pq->rear->next = pnew;
pq->rear = pnew; // 重新設置隊列尾
pq->count++; // 隊列計數 + 1
return true;
}
bool DeQueue(Item * pitem, Queue * pq)
{
Node * pt;
if (QueueIsEmpty(pq))
return false;
CopyToItem(pq->front, pitem); // 拷貝數據
pt = pq->front; // 獲取要刪除的鏈表項
pq->front = pt->next; // 隊列頭指向下一個鏈表項
free(pt);
pq->count--;
if (pq->count == 0)
pq->rear = NULL;
return true;
}
// 通過循環調用隊列頭部刪除函數來實現
void EmptyTheQueue(Queue * pq)
{
Item temp;
while (!QueueIsEmpty(pq))
DeQueue(&temp, pq);
}
static void CopyToNode(Item item, Node * pnode)
{
pnode->item = item;
}
static void CopyToItem(Node * pnode, Item * pitem)
{
*pitem = pnode->item;
}
測試隊列功能的驅動程序:
#include <stdio.h>
#include "queue/queue.h"
int main(void)
{
Queue line; // 創建隊列
Item temp;
int ch;
InitializeQueue(&line);
puts("Test the queue interface. Type a to add a value,");
puts("Type d to delete a value, and type q to quit.");
while ((ch = getchar()) != 'q')
{
if (ch != 'a' && ch != 'd') // 忽略其他輸出
continue;
if (ch == 'a')
{
puts("Integer to add:");
scanf("%d", &temp);
if (QueueIsFull(&line))
puts("The queue is full!");
else
{
printf("Putting %d into queue.\n", temp);
EnQueue(temp, &line);
}
}
else
{
if (QueueIsEmpty(&line))
puts("The queue is empty!");
else
{
DeQueue(&temp, &line);
printf("Removing %d from queue.\n", temp);
}
}
printf("%d items in queue\n", QueueItemCount(&line));
puts("a to add, d to delete, q to quit");
}
puts("Now free the queue...");
EmptyTheQueue(&line);
puts("All done!");
return 0;
}
程序執行結果如下
1 數據進入隊列
2 隊列讀出數據