《数据结构与算法》阅读笔记——表(内容太多了慢慢更)

1.表与链表:表:连续存储一组数的数据结构。假定表中存在着某个元素i,则i的前一个元素为i的前驱元素,i的后一个元素为i的后继元素。
对表的操作:1.PrintList:输出表
2.MakeEmpty:创建空表
3.Find返回关键字首次出现的位置(值得注意的是返回的值是该关键字所在的位置,比如第一个就是1,第二个就是2,不用减一。)
4.Insert和Delete:一般是从表的某个位置插入和删除某个关键字。
5.FindKth:返回某个位置上的元素。
关于链表:
链表是什么:不连续储存数据的表,链表里的元素的内存地址并不连续,每一个元素的后面都跟着一个地址指针来指向下一个元素。
为什么要使用链表:链表里的每个元素相比于表,还多储存了一个指针变量,那链表的存在的意义是什么呢?
在程序中,对表的所有的操作都可以使用数组来实现,通过数组实现使得PrintList和Find以线性时间执行,而FindKth则花费常数时间执行,然而,插入和删除所使用的时间却是比较大的,
因为表的内存地址是连续的,所以当你插入或删除一个元素时,比如你在i位置插入或删除一个元素,那么i元素后面的元素的地址都要发生改变,否则这就内存地址就会出错。
(如果i=1的话,就麻烦了。
在这种情况下,链表不连续储存的优点就展现出来了,在增加或删除一个元素时,只要修改指针变量就可以了,大量减少程序执行的时间。
链表中最后一个元素的指针指向Null。(但同样执行FindKth操作时,链表的执行效率不如表。)
为了更方便,后面还衍生出来了双链表和循环链表:
双链表:可以以倒序的形式扫描链表,只需要在普通链表的附加一个域,使它包含指向前一个单元的指针即可。
循环链表:让最后的一个单元反过来直指第一个单元,可以有表头,也可以没有表头。
程序设计细节:
书上指出了三处会出问题的地方:
1.并不存在从所给定义出发在表的起始端插入元素的真正显性的方法。(?翻译得奇奇怪怪的,上下文完全没出现过相关内容)
2.从表的起始端实行删除是一个特殊情况:因为它改变了表的起始端,编程中的疏忽将会造成表的丢失(?可能是表的起始端有什么特殊的数据,但我看了半天,书上没提到过这种特殊数据。)
3.一般的删除:删除的算法要求我们记住被删除元素前面的表元。(?是因为前面的表元中包含了指针吗)
(这三句话真的意义不明。)
不明白也没事,我自己也不是太明白,但书上立刻就给了这三个问题的解决方法:留出一个标志节点——表头(header)
下面这一串里面用了很多函数,看不懂也没事,我等下会再复制一遍。(书上说是例程,但看着应该是和函数差不多的东西。)
链表的类型声明:

#ifndef _List_H//

struct Node;
struct struct Node * PtrToNode;
typedef PtrToNode List;
typedef PtrToNode Position;

List MakeEmpty( List L );//这个函数就是上面说的那个基础函数,创建一个空表。
int IsEmpty( List L );//等下会编写的第一个函数,不能理解没事,先放着
int IsLast( Position P, List L );//等下会编写的第二个函数
Position Find( ElementType X, List L );//等下会编写的第三个函数
void Delete( ElemntType X, List L);//等下会编写的第四个函数
Position FindPrevious( ElementType X, List L );//不说了,都懂
void Insert( ElementType X, List L,Position P);//插入函数
void DeleteList( List L);//删除函数
Position Header( List L);//找表头,这个函数上面介绍了
Position First( List L);//上同
Position Advance( Position P );//上同
ElementType Retrieve( Position P);//检索位置P上的元素
#endif 
struct Node
{
  ElementType Element;
  Position Next;
}

第一个函数(例程):测试一个链表是否为空表:

int IsEmpty( List L )
{
  return L->Next == NULL;//最后一个元素的指针是否指向NULL,是则返回1,否则返回0
}

第二个函数(例程):测试当前位置P是否为链表的末尾:

int IsLast( Position P, List L )
{
  return P->Next == NULL;//检测P指向下一个位置的指针是否指向NULL,是则返回1,否则返回0
}

第三个函数(例程):Find:在链表L中寻找某个元素,并返回它的位置,

Position P;
Find( ElementType X, List L )
{
  Position P;//声明指针变量P

  P = L->Next;//将P赋值为链表L的第一个位置
  while( P != NULL && P->Element != X )//当P不处于末尾且没有找到元素X时,P指向下一个元素
    P = P->Next;
  return P;//返回结果P,即X的位置
}

第四个函数(例程):链表的删除:Delete

void Delete( ElementType X,List L )
{
  Position P,TmpCell;//声明指针变量P,TmpCell

  P = FindPrevious( X, L );//P返回为X的前驱元
  
  if( !IsLast( P , L ))
  {
     TmpCell = P->Next;//TmpCell为P的指向的下一个位置
     P->Next = Tmpcell->Next;
//P指向的下一个位置是TmpCell指向的下一个位置,这样就空出来了一个位置,
//P直接指向了P后面的第二个位置,P的下一个位置(TmpCell指针对应的的位置)没有指针对应。
     free( Tmpcell );//释放TmpCell指针上位置的内存空间
   }
}

第五个函数(例程):FindPrevious:找出含有X的表元的前驱元P,实现删除:

Position FindPrevious( ElementType X, List L )
{
  Position P;
  P = L;
  while( P->Next != NULL && P->Next->Element != X )//P不是末尾的元素且P的下一个元素不是X
    P = P->Next;//P赋值为自己的下一个位置的指针
  return P;
}

第六个函数(例程):链表的插入:将要插入的元素与表L和指针位置P一起传入:

void Insert( ElementType X, List L, Position P )
{
  Position TmpCell;//声明指针变量TmpCell
  TmpCell = malloc( sizeof( struct Node ) );//TmpCell赋值为结构指针的地址
//malloc函数的作用是动态分配地址
  if( Tmpcell = NULL )
    FatalError( "Out of space!!!" );//输出“空间不足”

  TmpCell->Element = X;//将TmpCell所指的元素赋值为X
  TmpCell->Next = P->Next;//将P->Next保存到TmpCell->Next中
  P->Next = TmpCell;//将P->Next赋值为TmpCell,完成指针的插入
}

现在我们倒回来第一个函数:

#ifndef _List_H

struct Node;
struct struct Node * PtrToNode;
typedef PtrToNode List;
typedef PtrToNode Position;

List MakeEmpty( List L );//创建空表L
int IsEmpty( List L );//测试链表L是否为空表
int IsLast( Position P, List L );//测试表元P是否为链表的L的末尾元素
Position Find( ElementType X, List L );//在链表L中查找元素X,如果不存在,则返回空
void Delete( ElemntType X, List L);//在链表L中删除元素X
Position FindPrevious( ElementType X, List L );//找出链表L中的元素X的前驱元素并删除
void Insert( ElementType X, List L,Position P);//在链表L的P位置上插入元素X
void DeleteList( List L);//删除链表L
Position Header( List L);//返回表头的位置
Position First( List L);//返回L中的第一个元素
Position Advance( Position P );//返回表元P的前一个元素
ElementType Retrieve( Position P);//检索P位置
#endif 
struct Node//结构指针
{
  ElementType Element;
  Position Next;
}

除了上面的函数外,书上还指出了一些常见的错误:
memory access violation或segmentation violation:这类信息通查意味着有指针变量包含了伪地址,一个通常的原因就是初始化变量失败。
例如:下面的例子中,如果第一行出现了遗漏,变量P就初始化失败了:

void DeleteList( List L )
{
  Position P;
  p = L->Next;//第一行在这里
  L->Next = NULL;//L是最后一个元素
  while( P != NULL );
  {
    free(P);//释放P位置上的元素
    P = P->Next; //将P指向指向下下个位置
  }
}

第二种常见的错误:不清楚何时使用及何时不使用malloc:
关键是记住声明一个结构的指针并不意味着声明一个结构,而只是给出足够的空间给该结构使用。
如果想创建尚未被声明过的记录的唯一方法是使用malloc库函数,malloc(HowManyBytes)将使系统创建一个新的结构并返回指向该结构的指针,(要求包含stdlib.h头文件)
但如果你只是想使用一个指针变量沿着一个表行进,那就没有必要创建新的结构。
当有些空间不再需要时,你可以通过free命令通知系统来回收它。free(P)的结果是P正在指向的地址没变,但在该地址处而定数据此时已经无定义了。
接着书中提到一些关于表的删除的细节,书中所模拟的情况是这样的:
如果你从未对一个链表进行过删除操作,那么调用malloc的次数应该等于表的大小(若有表头就再加一),少一点你就不可能得到一个正常的程序,多一点你就会浪费空间并可能要浪费时间。
先对链表中进行一次删除后,再将该单元释放是一个好的想法,对于被释放的单元,需要一个临时的变量,因为如果你先撤销指针的话,就不能再引用这个单元了。这里书上举了一个错误的例子。

void DeleteList( List L )
{
  Position P,Tmp;//声明变量
  P = L->Next;//P为表头
  L->Next = NULL;//L的末尾为NULL
  while( P != NULL )//当P不处于链表末尾时
  {
    free(P);//释放P位置上的空间
    P = P->Next;//此时P上的空间已经被释放了,你已经调用不了P了,如果把这行代码打在删除的前面,就会错误地删除P的下一个元素
  }
}
书上提供了一种正确的删除方法,看之前不妨先理解理解前面的内容,看看和自己想的一不一样
void DeleteList( List L )
{
  Position P,Tmp;//声明变量
  P = L->Next;//P为表头
  L->Next = NULL;//L的末尾为NULL
  while( P != NULL )//当P不处于链表末尾时
  {
    Tmp = P->Next;//Tmp为P的下一个指针,Tmp为加入的一个临时变量
    free(P);//释放P位置上的空间
    P = Tmp;//将P赋值为自己的下一个指针,成功删除元素
  }
}

接着书上为我们介绍了三个使用链表的例子:
1.多项式ADT:
对于F(x)=(A1X1)+(A2X2)+(A3X3)+.......+(AnXn)
如果大部分系数非0,我们可以用一个简单数组来储存这些系数,然后可以编写一些对多项式进行加,减,乘,微分及其他操作,下面是例子:
1.类型声明:

typedef struct
{
  int CoeffArray[ MaxDegree + 1 ];
  int HighPower;
} * polynomial;
posted @ 2023-03-06 20:24  apeiriaDolce  阅读(23)  评论(0编辑  收藏  举报