链表--学习笔记

1. 链表概念

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。由一系列节点(Node)通过指针连接而成(像火车),从一个头节点(Head)开始,头节点作为链表的入口点,它包含了对第一个节点的引用。最后一个节点的指针指向一个空值(NULL),表示链表的结束。一个节点有数据域和指针域(节点相当于数组的元素)

【存储】链表在内存中的存储方式则是随机存储(见缝插针),每一个节点分布在内存的不同位置,依靠指针关联起来。(只要有足够的内存空间,就能为链表分配内存)

【优缺点】:相对于顺序储存(例如数组):

优点:链表的插入操作更快( O(1) ),无需预先分配内存空间

缺点:失去了随机读取的优点(需要从头节点开始依次遍历,直到找到目标节点。),内存消耗较大(每个节点都需要存储指向下一个节点的指针)。

2. 链表分类

【1】带头结点和不带头结点:头结点是第一个节点的数据域不使用,只使用next域

【2】头插法和尾插法

头插法:在头指针的后面插入新节点,相当于后进先出(栈)。因为链表是从头部开始遍历

尾插法::在尾部插入新节点,相当于后进后出(队列)。

【3】循环链表,单向链表,双向链表

虽然链表中存在这么多结构,但是在实际中最常用的就只有两种结构:

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单。

3. 单链表的实现

包含节点的创建,插入,删除,修改,查找,遍历打印

相关代码的示意图

【1】节点的插入

【2】删除节点

有人可能会不理解,为什么要搞两个指针来完成删除,或者其他操作,因为我们要用其他指针来保存节点的信息,需要两个指针来分别跟踪当前节点和前一个节点,这样才能正确地修改链表结构。我的理解是,把这个指针当做是这个结点,总不能对一个不知道名字的节点进行操作吧。

#include <stdio.h>
#include <stdlib.h>

// 定义链表节点结构
typedef struct node_s 
{
    int         data;
    struct      node_s* next;
}node_t;

//定义头指针和尾指针
typedef struct linkedlist_s
{
        node_t  *head;
        node_t  *tail;
}linkedlist_t;

void link_init(linkedlist_t *list);
node_t* link_create_node(int data);
void link_insert_head(linkedlist_t *link, int data);
void link_insert_tail(linkedlist_t *list, int data);
void link_delete_node(linkedlist_t *list, int value);
void link_update_node(linkedlist_t *list, int oldvalue, int newvalue);
node_t* link_search_node(linkedlist_t *list, int value);
void link_print(linkedlist_t *list);


int main (int argc, char **argv)
{
        linkedlist_t list;

        link_init(&list);

    link_insert_head(&list, 10);
        link_insert_tail(&list, 20);
    link_insert_tail(&list, 30);
    link_insert_head(&list, 5);

    printf("链表内容: ");
    link_print(&list);

    link_update_node(&list, 20, 25);
    printf("更新后的链表内容: ");
    link_print(&list);

    link_delete_node(&list, 10);
    printf("删除后的链表内容: ");
    link_print(&list);

    node_t* node = link_search_node(&list, 25);
    if (node != NULL) {
        printf("找到节点: %d\n", node->data);
    } else {
        printf("未找到节点\n");
    }

        return 0;
} 

//初始化头,尾指针
void link_init(linkedlist_t *list)
{
        list->head = NULL;
        list->tail = NULL;
}

//创建一个节点
node_t* link_create_node(int data)
{
        node_t* new_node= malloc( sizeof(node_t) );
        if( new_node == NULL)
        {
                printf("分配失败\n");
                exit(EXIT_FAILURE);
        }
        new_node->data = data;
        new_node->next = NULL;
        return new_node;
}

//头插法
void link_insert_head(linkedlist_t *list, int data)
{
        node_t *new_node = link_create_node(data);
        if( list->head == NULL)
        {
                list->head = new_node;
                list->tail = new_node;
        }
        new_node->next = list->head;//指向之前的头结点
        list->head = new_node;//头指针指向新插入的结点
}

//尾插法
void link_insert_tail(linkedlist_t *list, int data)
{
         node_t *new_node = link_create_node(data);
         if( list->head == NULL)
         {
                list->head = new_node;  
                list->tail = new_node;
         }
         list->tail->next = new_node;
         list->tail = new_node;
}

//删除某个特定节点
void link_delete_node(linkedlist_t *list, int value)
{
        if(list->head == NULL)
                return;
        node_t *temp = list->head;
        if(temp->data = value)//如果是头节点
        {
                list->head = temp->next;
                if(list->head == NULL)
                {
                        list->tail = NULL;//说明只有一个节点,删完就没有了
                }

                free(temp);
                return;
        }

        node_t *prev = NULL;
        while(temp != NULL && temp->data != value)
        {
                prev = temp;//没有找到继续往后找
                temp = temp->next;
        }
        if( temp == NULL) 
                return;
        prev->next = temp->next;//找到后,前一个节点指向当前节点的后一个节点
        if( prev->next == NULL)
                list->tail = prev;
        free(temp);
}

//修改某个节点的值
void link_update_node(linkedlist_t *list, int oldvalue, int newvalue)
{
        node_t *temp = list->head;
        while( temp != NULL )
        {
                if( temp->data = oldvalue)
                {
                        temp->data = newvalue;
                        return;
                }
                temp = temp->next;//否则一直找
        }
        printf("节点%d未找到\n",oldvalue);
}

//按值查找节点
node_t* link_search_node(linkedlist_t *list, int value)
{
        node_t *temp = list->head;
        while( temp !=NULL )
        {
                if( temp->data == value )
                {
                        return temp;
                }
                temp = temp->next;
        }
        printf("没有找到相关结点\n");
        return NULL;
}

//遍历链表的每个节点并打印
void link_print(linkedlist_t *list)
{

        node_t *temp = list->head;

        while( temp != NULL )
        {
                printf("%d->",temp->data);
                temp = temp->next;
        }
        printf("NULL\n");//打印完毕
}

4. 双向链表

【1】为什么使用双向链表

单链表的结点中只有一个指向其后继的指针,使得单链表要访问某个结点的前驱结点时,只能从头开始遍历,访问后驱结点的复杂度为O(1),访问前驱结点的复杂度为O(n)。(例如,若实际问题中需要频繁地查找某个结点的前驱结点,使用单链表存储数据显然没有优势,因为单链表的强项是从前往后查找目标元素,不擅长从后往前查找元素。)为了克服上述缺点,引入了双链表。

【2】定义

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。

双向链表中各节点包含以下 3 部分信息:

  1. 指针域:用于指向当前节点的直接前驱节点;
  2. 数据域:用于存储数据元素。
  3. 指针域:用于指向当前节点的直接后继节点;

【3】特点

1.每次在插入或删除某个节点时, 需要处理四个节点的引用, 而不是两个,实现起来要困难一些;

2.相对于单向链表, 必然占用内存空间更大一些;

3.既可以从头遍历到尾, 又可以从尾遍历到头;

【4】结构体

// 定义双向链表节点结构
typedef struct line{
    struct line * prior; //指向直接前趋
    int data;
    struct line * next; //指向直接后继
}Line;

// 定义双向链表结构
typedef struct {
    Node* head;
    Node* tail;
} DoublyLinkedList;

参考链接: 数据结构之链表---从入门到精通

  • 56
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值