对表的一点小心得

kevin_pan 2003-10-23 09:24:09
进期由于参加CSDN算法板的数据结构小组,我对表这种数据结构做了一些简单的学习,其中一直困扰我的问题并不是对表的基本操作,而是对表的临界状态(空表和非法表)的区别,下面我就简单做一个小结.

问题的提出:
按照常理来说,非法表和空表的区别应该在于非法表是没有从堆中分配内存的,而空表是一种合法的表,只是它的内容为空,我们可以对它进行任何表的基本操作。以严老师的书中所设计的带头结点的数据结构为例子

typedef struct Lnode{
ElemType data;
struct LNode* next;
}LNode,*Link;

typedef struct{
Link head,tail;
Int length;
}LinkList;

当声明一个表头的时候我们只是得到一个内容不确定的表头,所以我们应该在声明后马上对这个表初始化,使之代表一个合法的表,以下是我根据书上的数据结构写的InitList和CreatList函数

Status InitList(LinkList *L)
{
L->head = NULL;
L->tail = NULL;
L->length = 0;
}

Status CreateList(LinkList *L)
{
Link p,q;
ElemType x;
int len = 0;
scanf(“%d”,&x);
while(x != ‘/0’){
p = (Link)malloc(sizeof(LNode));
if(*L->head == NULL) L = p;
else q->next = p;
q = p;
len++;
scanf(“%d”,&x);
}
if(q == NULL){
L->head = NULL;
L->tail = NULL;
L->length = len;
return OK;
}
else{
q->next = NULL;
L->tail = q;
L->length = len;
return OK;
}
}

这样大家就可以看出,我们对一个链表的初始化或者创建其实都隐含着一个约定,即如果我们没有输入任何元素,那么我们就认为经过初始化或者创建操作,这个表就已经是合法的了,那么我们设计的非法表的检测是如何的呢

Status IsValidList(LinkList L)
{
if(L->head == NULL && L->tail == NULL && L->length == 0)
return TRUE;
return ERROR;
}

这样来看问题就明显了,链表的用途就是动态分配内存,它是以结点的创建需求为前提,当没有任何插入需求的时候我们就不应该为链表分配内存,所以我们不应该改变链表的任何东西即保持L->head = NULL, L->tail =- NULL, L->length = 0;这样我们就没法设计一个ListEmpty函数来检验链表是否为空,因为空表和非法表已经完全相同了。这个现象不仅在表这种数据结构中存在,在树等其他结构中也有,其原因就是链表这种存储方式是面向结点的。

问题的分析:
对于表这种数据结构有两种存储结构,顺序存储和链式存储,链式存储又分为有头结点和无头结点两类。顺序表的定义如下

typedef struct {
ElemType *elem
int length;
int size;
}Sq_List;

对于这种情况根本不存在上述问题,因为初始化的时候就分配LIST_INIT_SIZE大小的内存了,所以可以根据判断elem来判断合法性,根据length判断集合是否为空。

对于链式存储结构前面我们已经讨论过有头结点的一种情况了,而没有头结点的链表情况更为恶劣,如下所述:

typedef struct LNode{
ElemType data;
struct LNode* next;
}*LinkList,LNode,*Link;

对于一个这样的链表来说我们更不能根据指向头结点的指针是否为空来区分非法表和空表了。

问题的解决:
以上提出的问题主要就是集中在:对于空表和非法表的迷茫。
我提出的解决办法,其实在CSDN上大家都一直这么用,就是把头结点定义为

typedef struct{
Link head,tail;
int length;
}*LinkList;

这样一来对于一个有头结点的表来说初始化操作就是对表的头结点分配内存

Status InitList(LintList *L)
{
L = (LinkList)malloc(sizeof(*LinkList));
If(!L)
return ERROR;
L->head = NULL;
L->tail = NULL;
L->length = 0;
return OK;
}

Status ListEmpty(LinkList L)
{
assert(L);
return L->length ? TRUE:FALSE;
}

void main(void)
{
LinkList newlist = NULL; //为了判断非法表,我们在初始化这个变量的时
// 候就附值为NULL
InitList(&L);
……
}

下面来讨论一下没有头结点的链表,这种情况是最头疼的,因为链表是面向结点的,所以这种链表根本没法建立一个空表,我处理这种链表的方法就是把表指针的值付上“1”,这样就能区别了。

typedef struct LNode{
ElemType data;
Struct LNode *next;
}*LinkList,Link,LNode;

Status InitList(LinkList *L)
{
L = 1;
return Ok;
}

void main(void)
{
LinkList L = NULL;
InitList(*L)
}

我想归根结底始终存在这种迷茫的原因是像我这样的初学者始终不能很心安理得的声明一个指针:) 这样就很难区分根据内存分配来区分非法表和合法表,所以觉得初始化的时候很别扭。

以上就是我对这个问题的一点点心得,其实很弱,不过确实困饶了我好久,写出来爽一点,希望大家不要笑话。

另外向组长5510道歉,由于这个小问题耽误了很多时间没有按时交付答案。
...全文
30 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
kevin_pan 2003-10-24
  • 打赏
  • 举报
回复
谢谢各位斑竹捧场,问题的实质确实是指针的问题,其实就是从系统应用者来说根本无法判断一个内存地址是否合法。
严老师的书上没有介绍“哨兵”的概念,所以让人费解:)
frankzch 2003-10-24
  • 打赏
  • 举报
回复
分析的这么仔细,PFPF

你说的其实是关于指针的问题,指针如果没有初始化就引用(即你的表非法的情况),容易引起系统的崩溃
heartup 2003-10-24
  • 打赏
  • 举报
回复
对对,而且有的时候即使题目中要求不用头节点,如果我们在实现中增加一个头节点,用完后在删除,可以省去许多麻烦.
ZhangYv 2003-10-23
  • 打赏
  • 举报
回复
一般来说,使用带表头节点的要比不带头节点的要好.带头节点的结构优势是能更容易处理各种边界情况.

33,008

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧