1. 程式人生 > >重刷資料結構,小題大做,——難道非要頭結點嗎?

重刷資料結構,小題大做,——難道非要頭結點嗎?

  1. 由於開始結點的位置被存放在頭結點的指標域中,所以在連結串列的第一個位置上的操作和在表的其他位置上的操作一致,無須進行特殊處理。
  2. 無論連結串列是否為空,其頭指標是指向頭結點的非空指標,因此空表和非空表的處理也就一致了。
上面的兩點的意思是:1.不管是在第一個位置還是其它位置插入一個新的結點,都是在一個結點之後插入(在第一個位置插入新結點,是在頭結點後插入);如果沒有頭結點,在第一個位置插入結點是直接給頭指標賦值,而在其它位置插入結點是將結點插入到一個結點之後,這兩種處理是不同的。刪除結點也類似; 2.空表和非空表中都有結點,所以對空表的處理也就一致了。 你可能會疑惑:難道非得要頭指標嗎?難道沒有頭結點,插入、刪除、空表的處理就不一致了嗎?(貌似有一些同學和我有同樣的疑問) 不一致問題的來源
教材給出的插入和刪除操作的演算法如下: 插入結點
s->next = p->next; p->next = s; 刪除結點
p->next = q->next; free(q);         從上面可以看出,插入和刪除結點的核心操作在於改變結點的next指標域指向。而上面都是通過指向結點的指標來改變結點的next域的,比如p->next = q->next。在沒有頭結點的情況下,如果要插入和刪除第一個結點,需要修改頭指標。而頭指標不屬於任何結點,不能通過修改某個結點的next指標域來修改頭指標,所以得特殊處理,這就帶來了操作的不一致性。頭指標、結點的next指標域,它們雖然都是同一種類型的指標,但得分開處理。         所以問題的來源在於:通過指向結點的指標來訪問結點的next指標域,這種方法不適合頭指標。
書上的解決方法
書上的解決方法就是引入頭結點。這樣即使是插入或刪除第一個結點,而已不需要修改頭指標,而只需要通過頭指標修改頭結點的next指標域。這樣操作都一致了。
新的解決方法 前面說了,問題的來源是“通過指向結點的指標來訪問結點的next指標域,這種方法不適合頭指標” 。這種訪問方法把頭指標和結點的next指標當做兩個不同的東西。那我們為什麼不把頭指標和結點的next指標域看做同一種東西呢?它們本就是同樣的資料型別啊!這樣不是可以一致處理了。把它們都看作指向結點的指標,就可以通過指向指標的指標來修改和訪問了。新的演算法如下: 插入
s->next = *p;    //p指向指標的指標 *p = s; 刪除

*p = q->next;    //p指向指標的指標 free(q); 小弟通過上面的思路寫程式碼證實了,這種方法可以達到同樣的效果。不需要新增頭結點,而且操作一致。 程式碼 下面是兩份程式碼,一份是教材上有頭結點的版本,一份是本文提出的無頭結點的版本。程式碼都通過測試!
/*
*有頭結點版本
*作者:善良超哥哥
*時間:2014-8-16
*/
//LinkList.h
#ifndef _LIMKLIST_H_
#define _LINKLIST_H_

typedef struct LNode{
	char	data;
	struct LNode *next;
}LNode;

typedef struct{
	LNode	*head;	//指向第一個結點的指標
	int		len;	//連結串列長度
}LinkList;

//建立連結串列,尾插法
void CreateList(LinkList *L);
//在第i個位置插入元素e
int ListInsert(LinkList *L,int i,char e);
//刪除第i個位置的元素,並用*e返回
int ListDelete(LinkList *L,int i,char *e);
//遍歷列印連結串列中的所有元素
void ListPrint(LinkList *L);

#endif
//LinkList.c
/*
有頭結點的連結串列實現
*/

#include "LinkList.h"
#include <stdio.h>
#include <stdlib.h>

//建立連結串列,尾插法
void CreateList(LinkList *L)
{
	L->head = malloc(sizeof(LNode));//建立頭結點
	L->head->next = NULL;
	L->len = 0;
	
	//開始輸入字元,建立連結串列,以#結束
	char c;
	LNode *p = L->head;
	while((c = getchar()) != '#')
	{
		LNode *s = malloc(sizeof(LNode));
		s->data = c;
		p->next = s;
		p = s;
		
		++(L->len);
	}
	p->next = NULL;
}

//在第i個位置插入元素e
int ListInsert(LinkList *L,int i,char e)
{
	if(i < 1 || i > L->len || L == NULL)
	{
		return 0;//失敗
	}
	LNode *p = L->head;
	for(int j = 1; j < i; j++)
	{
		p = p->next;
	}
	
	LNode *s = malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	
	++(L->len);
	
	return 1;
}

//刪除第i個位置的元素,並用*返回
int ListDelete(LinkList *L,int i,char *e)
{
	if(L == NULL || i < 1 || i >= L->len)
	{
		return 0;//失敗了
	}
	
	LNode *p = L->head;
	for(int j = 1; j < i; j++)
	{
		p = p->next;
	}
	//刪除p指標指向的結點的下一個結點
	LNode *q = p->next;
	p->next = q->next;
	free(q);
	--(L->len);
	
	return 1;
}

//遍歷列印連結串列中的所有元素
void ListPrint(LinkList *L)
{
	LNode *p = L->head->next;
	while(p!=NULL)
	{
		printf("%c",p->data);
		p = p->next;
	}
	printf("\n");
}
無頭結點版本:
/*
*無頭結點版本
*作者:善良超哥哥
*時間:2014-8-16
*/
//LinkList.h
#ifndef _LIMKLIST_H_
#define _LINKLIST_H_

typedef struct LNode{
	char	data;
	struct LNode *next;
}LNode;

typedef struct{
	LNode	*head;	//指向第一個結點的指標
	int		len;	//連結串列長度
}LinkList;

//建立連結串列,尾插法
void CreateList(LinkList *L);
//在第i個位置插入元素e
int ListInsert(LinkList *L,int i,char e);
//刪除第i個位置的元素,並用*e返回
int ListDelete(LinkList *L,int i,char *e);
//遍歷列印連結串列中的所有元素
void ListPrint(LinkList *L);

#endif

//LinkList.c #include "LinkList.h" #include <stdio.h> #include <stdlib.h> //建立連結串列,尾插法 void CreateList(LinkList *L) { L->head = NULL; L->len = 0; //開始輸入字元,建立連結串列,以#結束 char c; LNode **p = &(L->head); while((c = getchar()) != '#') { LNode *s = malloc(sizeof(LNode)); s->data = c; (*p) = s; p = &(s->next); ++(L->len); } (*p) = NULL; } //在第i個位置插入元素e int ListInsert(LinkList *L,int i,char e) { if(i < 1 || i > L->len || L == NULL) { return 0;//失敗 } LNode **p = &(L->head); for(int j = 1; j < i; j++) { p = &((*p)->next); } LNode *s = malloc(sizeof(LNode)); s->data = e; s->next = *p; *p = s; ++(L->len); return 1; } //刪除第i個位置的元素,並用*返回 int ListDelete(LinkList *L,int i,char *e) { if(L == NULL || i < 1 || i >= L->len) { return 0;//失敗了 } LNode **p = &(L->head); for(int j = 1; j < i; j++) { p = &((*p)->next); } //刪除p指標指向的結點 LNode *q = *p; *p = q->next; *e = q->data; free(q); --(L->len); return 1; } //遍歷列印連結串列中的所有元素 void ListPrint(LinkList *L) { LNode *p = L->head; while(p!=NULL) { printf("%c",p->data); p = p->next; } printf("\n"); }
演算法對比