数据结构——链表

185 阅读4分钟

1. 链表的概念

顺序表 → 静态存储分配 → 事先确定容量

链表 → 动态存储分配 → 运行时分配空间

1.1 单链表:线性表的链接存储结构

存储思想:用一组任意的存储单元存放线性表的元素——不连续、零散分布

1.2 单链表存储特点

  • 逻辑次序和物理次序不一定相同

  • 元素之间的逻辑关系用指针表示

单链表是由若干结点构成的,节点只有一个指针域。

1.3 单链表的结点结构

1653479905(1).jpg

  • data:数据域,存储数据元素

  • next:指针域,存储指向后继结点的地址

typedef struct node
{
    DataType data;  // 数据域
    struct node *next;  // 指针域
}Node,*Link;
  • Node st; 等价于 struct node st;

  • Link p; 等价于 struct node *p; (p=&st;)

  • Node变成了一个类型,可以去声明一个结构体变量;Link也变成了一个类型,用来声明一个指向结构体的指针

1.4 如何申请一个结点?

p = (Link)malloc(sizeof(Node)); 等价于 p = (struct node *)malloc(sizeof(Node));

1.5 如何引用数据元素?

(*p).data / p -> data

1.6 如何引用指针域?

p -> next

1.7 什么是存储结构?

数据元素之间的逻辑关系的表示,将实际存储地址抽象。

头指针:指向第一个结点的地址

尾标志:终端结点的指针域为空

空表:head=NULL

非空表:

1653480937(1).jpg

如何将空表与非空表统一?——引入头结点

头结点:在单链表的第一个元素结点之前附设一个类型相同的节点

1653481152(1).jpg

2. 单链表的实现

2.1 单链表的遍历操作

操作接口:void displayNode(Link head);

void displayNode(Link head)
{
    p = head->next;
    while(p != NULL)
    {
        printf("%d", p->data);
        p = p->next;  
    }
}

p = p->next语句改成 p++ 能否完成指针后移? 不可以!

链表中结点的存储不是连续的,是零散的!

2.2 求单链表的元素个数

操作接口:int length(Link head);

int length(Link head)
{
    p = head->next;  
    count = 0;    //初始化指针p和累加器count
    while(p != NULL)
    {
        p = p->next;  //指针p后移
        count ++;
    }
    return count;  //注意count的初始化和返回值之间的关系
}

2.3 单链表的查找操作

int queryNode(Link head, DataType x)  //也可以改成bool类型
{
    p = head->next;
    count = 0;
    while(p != NULL)
    {
        if(p->data == x)
        {
            print(data);  //找到则调用输出函数,并提前返回true
            return ture;
        }
        p = p->next;
    }
    //如果循环结束了,说明没有找到
    return false;
}

2.4 单链表的插入操作

操作接口:void insertNode(Link head, int i, DataType x);

bool insertNode(Link head, int i, DataType x)
{
    p = head;  //指针p指向头结点
    count = 0;
    while(p != NULL && count < i-1)  //查找第i-1个结点
    {
        p = p->next;
        count++;
    }
    if(p == NULL)
        return false;  //没有找到第i-1个结点
    else{
        node = (Link)malloc(sizeof(Node));  //申请一个结点node
        node->data = x;
        node->next = p->next;  //结点node插入结点p之后
        p->next = node;
        return true;
    }
}

2.5 创建一个单链表——头插法

操作接口:Link newList(DataType a[], int n);

头插法:将待插入结点插在头结点的后面

template<class DataType>
Link newList(DataType a[], int n)
{
    head = (Link)malloc(sizeof(Node));  // 创建头结点
    head->next = NULL;
    for(i=0;i<n;i++)  //创建后续结点
    {
        node = (Link)malloc(sizeof(Node));
        node->data = a[i];
        node->next = head->next;
        head->next = node;
    }
    return head;
}

2.6 创建一个单链表——尾插法

操作接口:Link newList(DataType a[], int n);

尾插法:将待插入结点插在终端结点的后面

Link newList(DataType a[], int n)
{
    head = (Link)malloc(sizeof(Node));  // 创建头结点
    head->next = NULL;
    rear = head;  //尾指针初始化
    for(i=0;i<n;i++)  
    {
        node = (Link)malloc(sizeof(Node));
        node->data = a[i];
        // 每次创建一个结点都把next域设为空,node->next = NULL;就不用写下面那一句,不会出现问题
        rear->next = node;
        rear = node;
    }
    rear->next = NULL;  //很关键!!!
    return head;
}

每次创建一个结点时,将结点数据域指针域全部初始化,指针域初始化为空,养成好的编程习惯!

2.7 单链表节点的删除

操作接口:bool deleteNode(Link head, DataType x);

bool deleteNode(Link head, DataType x)
{
    if(head == NULL || head->next == NULL){  //链表为空表
        return false;
    }
    p = head->next;  //初始化,p、q两个指针一前一后
    q = head;
    while(p!=NULL){
        if(p->data==x){  //找到x结点,删除这个结点并提前返回
            q->next = p->next;
            free(p);
            return true;
        }
        else{  //p的data域不等于x,则继续向后找
            q = p;
            p = p->next;
        }
    }
    //如果循环结束了,说明没有找到和x相等的结点
    return false;
}

2.8 单链表的释放

操作接口:void clearLink(Link head);

void clearLink(Link head){
    while(head->next !== NULL){
        p = head;
        head = head->next;
        free(p);
     }
}

3. 循环链表

将单链表的首尾相接,将终端结点的指针域由空指针改为指向头结点,构成单循环链表,简称循环链表

循环链表的特点:没有明显的尾端 → 如何避免死循环?——修改循环条件

循环条件:(其他操作和单链表一样,除了循环退出条件)

p != NULLp != head

p->next != NULLp->next != head

4. 双向链表

在单链表的每个结点中再设置一个指向其前驱结点的指针域。(占用更多的存储空间)

结点结构:

1653487555(1).jpg

  • data:数据域,存储数据元素

  • prior:指针域,存储该节点的前趋结点地址

  • next:指针域,存储该节点的后趋结点地址