重刷数据结构,小题大做,——难道非要头结点吗?

数据结构教材上讲线性表的链式表示时,通常都会引入一个头结点。

带头结点的链表示意图如下:

1.空链表

2.有三个结点的单链表

按照书上的说法,引入头结点有一下两个优点:

  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");

}

算法对比

重刷数据结构,小题大做,——难道非要头结点吗?,布布扣,bubuko.com

时间: 2024-08-26 23:36:21

重刷数据结构,小题大做,——难道非要头结点吗?的相关文章

完全二叉树的链式存储结构的转化 &amp; 非递归中序遍历二叉树

1 /* 2 * 二叉树 3 * 4 * (将完全二叉树的数组形式改为链表形式) 5 * 6 * 1 7 * 2 3 8 * 4 5 6 7 9 * 8 10 * 11 */ 12 #include <iostream> 13 #define MAX 10 14 using namespace std; 15 16 typedef struct btnode{ 17 int data; 18 struct btnode * lchild; 19 struct btnode * rchild;

结构化、半结构化、非结构化数据

结构化数据.非结构化数据以及半结构化数据是对存储形式的一种数据类型分析 结构化数据.非结构化数据以及半结构化数据对比 类别 结构化数据 半结构化数据 非结构化数据 数据特征 数据结构字段含义确定,清晰 具有一定结构,但语义不够确定:自描述,数据结构和内容混杂在一起 杂乱无章的数据,很难按照一个概念去进行抽取,无规律性 典型例子 数据库中的表结构 邮件.HTML.报表.资源库 视频.音频.图片.图像.文档.文本等 数据模型 二维表 树.图 无 存储方案 高速存储应用需求.数据备份需求.数据共享需求

头结点

图1为线性表(ZHAO, QIAN, SUN, LI, ZHOU, WU, ZHENG, WANG)的逻辑状态.头指针 指示链表中第一个结点(即第一个数据元素的存储映像)的存储位置.同时,由于最后一个数据元素没有直接后继,则线性链表中最后一个结点的指针为“空”(NULL). 图1 线性链表的逻辑状态 由上述描述可见,单链表可由头指针来唯一确定,在C语言中可用“结构指针”来描述. [cpp] view plaincopy //-----线性表的单链表存储结构----- typedef struct

(含有头结点以及尾结点)单链表各类功能的实现

对单链表实现如下功能: void InitList(List *list); //初始化单链表 bool push_back(List *list,ElemType x); //尾插法 void show_seqlist(List *list); //显示链表内容 bool push_front(List *list,ElemType x);//头插法 bool pop_back(List *list); //尾删法 bool pop_front(List *list); //头删法 Node

设计鲁棒性的方法:输入一个链表的头结点,逆序遍历打印该链表出来

之前有过整理链表等的概念和基本算法.比较重要的是插入,删除,遍历,建表(尾插法,头插法) 回忆链表尾部插入结点:  1 #include <iostream> 2 using namespace std; 3  4 typedef struct Node{ 5     int data;//数据域 6     Node *next;//指针域 7 } Node, *List; 8  9 //在单链表的末位添加一个结点10 void addNode(List *head, int value)1

单链表的头指针、头结点与首元结点

继续我们昨天所说的单链表.单链表也是一种线性表,所以总得有个头有个尾.链表中第一个结点的存储位置叫做头指针,那么整个链表的存取就必须是从头指针开始进行了.之后的每一个结点,其实就是上一个的后继指针指向的位置.雅加达娱乐城 这里有个地方要注意,就是对头指针概念的理解,这个很重要.“链表中第一个结点的存储位置叫做头指针”,如果链表有头结点,那么头指针就是指向头结点数据域的指针.画一个图吧. 这个图看起来很清晰了.比如说头结点,我们就可以这么描述了: 头结点是为了操作的统一与方便而设立的,放在第一个元

队列的链式存储---链表实现(有头结点)

/* 队列的链式存储 */ /* with header */ /* with no typedef */ struct Node{ ElementType Ele; struct Node *Next; }; struct LinQ{ struct Node *rear; struct Node *front; }; struct LinQ * CreateQ( void ) { struct LinQ *Ptr; struct Node *header; Ptr = malloc(sizeo

链表----在链表中添加元素详解--使用链表的虚拟头结点

在上一小节中关于在链表中头部添加元素与在其他位置添加元素在逻辑上有所差别,这是由于我们在给链表添加元素时需要找到待添加元素位置的前一个元素所在的位置,但对于链表头来说,没有前置节点,因此在逻辑上就特殊一些,操作方式也就有所差别,需单独处理.为了针对头结点的操作方式与其他方式一致:接下来我们就一步一步引入今天的主题--使用虚拟头结点. 首先来看看之前的节点结构--第一个是头结点 相应的逻辑代码,感兴趣的可以看看我上一篇相关介绍,点击传送地址 为了能把关于头结点的操作与其他操作统一起来,我们来分析一

链表总的首元结点、头结点、头指针的区别

前言,:今天看书的时候,又复习了一下链表的知识点,但是标题上这个知识点老是弄混淆,所以接下来做一个小小的总结,给自己加深理解. 一.三者的基本概念; 1.首元结点:就是指链表中存储第一个数据元素a1的结点,如下表格所示结点ZHAO就是首元结点: L-----> 数据域和指针域-----> ZHAO+指针域---->   2.头结点:它是在首元结点之前附设的一个节点,其指针域指向首元结点.头结点的数据域可以不存储任何信息,也可以存储与数据元素类型的其他附加信息,例如,当数据元素为整数型时,