【数构】第二章 数组和链表

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yky__xukai/article/details/81661364

2.3.2单链表:

在单链表中每个结点有两个域:存放元素的域Element和存放指向后继结点的指针域Link

1.单链表的结点结构

typedef struct node{
    T Element;
    struct node* Link;
}Node;

这里,我们定义了单链表的结点类型Node(类型名称node)包括元素域和指针域。

但是C语言里面是不可以这样写的,因为在C语言和C++不一样、C语言不允许用户自定义的数据类型包含未确定的数据成分。在C语言的环境中,这里的元素类型T必须由用户在应用时用typedef语句定义为具体的类型。例如我们可以把T定义成int类型的,也可以定义成如下的Entry类型:

typedef struct entry{
    int Key;
    char Data;
}Entry;
typedef T Entry;

创建和显示元素:

typedef int T;
T* InputElement()
{
    static T a;
    scanf("%d",&a);
    return &a; 
}
void PrintElement(T x)
{
    printf("%d,x);
}

构造新结点:

Node* NewNode()//仅仅构造一个新结点
{
    Node* p = (Node*)malloc(sizeof(Node));
    p->Link = NULL;
    return p;
}
Node* NewNode1()//调用InputElement,允许用户从键盘输入元素值构造新结点
{
    Node* p = (Node*)malloc(sizeof(Node));
    p->Element = *InputElement();
    p->Link = NULL;
    return p;
}
Node* NewNode2(T x)//使用由用户提供的元素值x来构造新结点。
{
    Node* p = (Node*)malloc(sizeof(Node));
    p->Element = x;
    p->Link = NULL;
    return p;
}

2.单链表类型

我们可以简单的将单链表的类型List定义为:typedef Node* List、这里,类型为List是指向Node类型变量的指针类型。我们可以将first指针定义为List类型,即Node* 类型。

3.单链表的插入和删除

①在单链表指定结点p后面插入q结点:

q->Link = p->Link;
p->Link = q;

②删除p所指示的结点(假设q是p的前驱结点):

q->Link = p->Link;

在这里:我们要删除一个结点,我们必须知道他的前驱结点是多少。

4.建立单链表:

NewNode1():获取值,构造新结点,由p表示。

开始时链表是空表,所以新结点成为首节点,指针first指向该节点,之后新节点被加到表的已建成部分的尾部。指针r始终指向当前表的最后一个结点。

List BuildList()
{
    Node *p,*r=NULL,*first=NULL;
    char c;
    printf("Another element?y/n");
    while(c=getchar()=='\n');
    while(tolower(c)!='\n'){//将字母转换成小写字母,其函数原型在头文件ctype.h中
        p = NewNode1();
        if(first!=NULL)
            r->Link = p;
        else
            first = p;
        r = p;
        printf("Another element?y/n");
        while(c=getchar()=='\n');
    }
    return first;
}

5.输出单链表:

void PrintList(List first)
{
    printf("\nThe list contains: \n");
    for(;first;first=first->Link)
        PrintElement(first->Element);
    printf("\n\n");
}

7.清空单链表(比较难理解):

首先我们知道单链表是由动态变量构成的数据结构,当需要删除单链表中的全部元素而清空单链表的时候,必须使用free语句逐一释放每个结点占用的空间,回收后备使用。如果我们只是简单的把单链表的表头置为空,那么单链表的结点占用的空间并没有被回收,那么此时已经无法来访问这些结点,这些结点便成为垃圾。这是对内存的浪费,严重时会影响程序的正常运行。所以动态变量不用的时候应该加以回收。

清空单链表,并且回收:

void Clear(List * first)
{
    Node *p = *first;
    while(*first)
    {
        p = (*first)->Link;
        free(*first);
        *first = p;
    }
}

请注意,这里使用的函数参数first的类型是List*。我们知道类型List等同于类型Node*,是指向单链表结点的指针类型,那么List*就是指向该指针类型的指针类型。List*类型的变量中保存的是Node*类型的指针变量的地址。

也就是说,如果我们使用语句List lst=NULL;定义了一个链表对象,事实上我们仅定义了一个指针变量,并且初始化为NULL,即为空表,对于一个已经 构造好的非空单链表,如果需要使用Clear函数清除其中的元素,其调用方式为Clear(&lst);由于C语言的参数是传值的,即在函数调用的时候要将实在参数的值传递给形式参数。因此,这里将指针变量lst的地址传递给形式参数first。在Clear函数的函数体内,所有的操作都是通过*first来执行的。

请注意区分first和*first:

first是指向lst的指针变量,而*first就是指针变量lst。

如果函数定义为void Clear(List first);则相应的函数调用就成为了Clear(lst);即lst和first具有相同的类型。在函数调用发生的时候,指针变量lst的值被复制到first,两者都指向同一个单链表的起始结点。由于在Clear函数当中,只是用了形式参数first,因此当释放了单链表的全部的结点后,指针first变成NULL,但在主函数main当中定义的指针变量lst的值未改变不为NULL;但是仍然保持着原单链表的起始结点的地址。为们知道,事实上此时所有的结点都已经被释放,也许已经重新分配而作他用,因此lst的地址应该视为无效的,如果此时使用,将导致不可预见的后果。

由此可见:

将形参first的类型定义成List不仅是错误的,而且是危险的。

7.主函数main

void main()
{
    List lst;
    lst = BulidList();
    PrintList(lst);
    Clear(&lst);
    PrintList(lst);
}

2.3.3带表头结点的单链表

在单链表 前增加一个结点,称之为表头结点,表头结点的数据域不存放单链表的元素,它或者为空,或者用于存放实现算法所需的辅助数据。①:空表只有一个表头节点②:非空表:(多加上一个额外的空结点)

请区分“表头结点”和“头节点”,表头结点是添加的额外的结点,头结点是单链表的起始节点。在以后的讨论中我们可以看到,使用表头节点在实现某些单链表的运算时可以简化算法的描述。

2.3.4循环链表

单链表的另一种表示是循环链表,单循环链表是将单链表尾结点的指针域置为第一个结点的地址,不在是空,这样就可以从表的任意一个结点出发,均可以访问到表中的所有结点。

2.3.5双向链表

双向链表的每个结点包括三个域:Element、LLink、RLink。

猜你喜欢

转载自blog.csdn.net/yky__xukai/article/details/81661364