数据结构之链表

单链表

1. 单链表的定义

单链表是通过任一存储单元来存储线性表中的元素。这些存储单元可以是连续的也可以是不连续的。为了每个数据元素都能链接起来,每个数据元素中除了存储数据以及其它的信息外,还应该储存该元素直接后继(也就是该元素的下一个元素)的位置信息(也就是下一个元素的地址)。就像每个人手拉着手一样,每一个人的手都拉着下一个人的手。
数据元素和下一个数据元素的存储位置的信息组成一个“结点”。对于C语言来说,储存下一个节点的信息的的是指针。所以在C语言中每一个节点包含的信息为:
1. 数据域:储存数据
1. 指针域:存储下一个结点的信息。
C语言中结点的定义如下:

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

其中ElemType可以根据需要定义,当数据多于一个时,可以使用结构体来定义ElemType。

2. 头结点和头指针

这里可以举一个火车的例子,火车有一个车头,车头后面会有一个东西会将车头与下一节车厢连接起来。只要知道了车头就可以找到它的任意一节车厢。链表也是这样,有时会为了操作方便会在第一个节点之前增加一个节点,称为头结点,这个节点中可以存储一些与该链表相关的信息,比如:链表的长度,当然也可以不存储任何信息,指针与存储第一个节点的信息。这样做的目的是为了使第一个节点不在具有特殊性(对于尾节点来说,可以认为它的后驱为NULL),链表中的每一个有数据的节点都有前驱和后继。当然也可以不使用头结点,那么每一次只需要记住第一个节点即可的存储位置即可。
这里写图片描述
通常我们把链表中第一存储位置的指针称为头指针。有了这个头指针我们就可以得到第一个节点的信息,也可以得到下一个节点位置,从而得到下一个节点的信息。
头结点和头指针的不同点:

  • 头指针
    • 头指针是指链表的第一个节点的指针,若链表有头节点则是指向头结点的指针
    • 头指针具有表示作用,所以常常用头指针作为链表的名字
  • 头节点
    • 头节点是为了操作方便而设立的,放在第一个元素之前,其数据域通常是毫无意义的,当然也可以存放链表的长度和其他信息
    • 头结点使得第一个节点不具有特殊性,在插入和删除时能相对简单一些。

注意:头节点不是链表中必须要有的。

3. 单链表的建立

一个链表是有头尾的,所以单链表的建立也就分为头插法和尾插法建立链表。
所谓头插法:就是每一次都将新的节点称为第一个元素。
这里写图片描述
下面的是头插法创建链表的C语言的实现:

void CreateListHead(LinkList * L, int n) {
    LinkList p;
    int i;
    *L = (LinkList) malloc (sizeof(LNode));
    (*L)->next = NULL;
    for (i = 0; i < n; i++) {
        p = (LinkList) malloc(sizeof(LNode));
        p->data = i;
        p->next = (*L)->next;
        (*L)->next = p;
    }
}

尾插法:尾插法是将每一个元素插在链表的尾部,也就是说,每一次都将新的节点称为链表的最后一个节点。
尾插发创建链表是比较常用的创建链表的方法。
这里写图片描述
下面的是尾插发创建链表的实现:

void CreateListTail(LinkList * L, int n) {
    LinkList p, r;
    int i;
    *L = (LNode *) malloc(sizeof(LNode));
    r = *L;
    for (i = 0; i < n; i++) {
        p = (LinkList) malloc(sizeof(LNode));
        p->data = i;
        r->next = p;
        r = p;
    }
    r->next = NULL;
}

4. 读取链表中的值

链表就像一个链子,元素是一个一个链接起来的,要找第i个元素,那么就得从头开始往后找。
下面我们来先看一下单链表的遍历。

LinkList p = L->next;
while (p != NULL) {
    printf("%d\n", p->data);
    p = p->next;
}

单链表的遍历就是从第一个元素开始往后知道遇到NULL。
读取第i个元素是,从第一个元素开始遍历到第i个元素,但是当没到第i个时,已经遇到了NULL,那么证明i大于链表中元素的个数。代码如下:

// 读取
int GetElem(LinkList L, int i, int *e) {
    int j = 1;
    LinkList p;
    p = L->next;
    while (p && j < i) {
        p = p->next;
        ++j;
    }
    if (!p || j > i) {
        printf("读取的节点不存在!");
        return 0;
    }
    *e = p->data;
    return 1;
}

5. 删除

在一个链表中如果想要删除第i个节点,那么应该找到它的第i个节点的前一个节点,让它的next指向第i个节点之后的哪一个节点。如下图:
这里写图片描述
代码实现如下:

// 删除
int ListDelete(LinkList *L, int i, int *e) {
    int j = 1;
    LinkList p, q;
    p = *L;
    while (p->next != NULL && j < i) {
        p = p->next;
        ++j;
    } // 找到第i个节点的前一个节点
    if (!(p->next) || j < 1) {
        printf("要删除的元素不存在!");
            return 0;// 0代表失败
    }

    q = p->next;
    p->next = q->next;
    *e = q->data;
    free(q);
    return 1;// 1代表成功
}

6. 插入

单链表的插入也非常的简单,想要插入q节点在第二个位置,那么得找到第二个位置的前一个节点p,那么也就知道了第二个位置的节点p->next。那么首先让q->next = p->next,也就是让q指向p->next这个节点,然后再p->next = p,让p指向q。核心如下:

q->next = p->next;
p->next = q;

这里写图片描述
实现如下:

// 插入
int ListInsert(LinkList *L, int i, int e) {
    int j = 1;
    LinkList p, s;
    p = *L;
    while (p != NULL && j < i) {
        p = p->next;
        ++j;
    }
    if (p == NULL || j > i) {
        printf("要插入的位置不存在!!\n");
        return 0;
    }

    s = (LinkList) malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;
    p->next = s;
    return 1;
}

7. 还有一点:删除整个链表

当我们不在使用被创建的链表时,就要删除整个链表。这个过程也很简单。从头遍历到尾,把每个节点都free()。

int ClearList(LinkList *L) {
    LinkList p, q;
    p = (*L)->next;
    while (p) {
        q = p->next;
        free(p);
        p = q;
    }
    (*L) = NULL;
    return 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值