c primer plus 專題17:高級數據表示

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", &current->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 隊列讀出數據

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章