数据结构笔记 五:单链表

说明:本笔记依照《王道论坛-数据结构》视频内容整理。

一、定义

在这里插入图片描述
顺序表特点:

  • 优点:可随机存储,存储密度高。
  • 缺点:要求大片连续空间,改变容量不方便。

单链表特点:

  • 优点:不要求大片连续空间,改变容量方便。
  • 缺点:不可随机存取,要耗费一定空间存放指针。

二、单链表结点

typedef int ElemType;		    // 根据实际情况定义

typedef struct lNode{           // 定义单列表结点类型
    ElemType data;              // 每个结点存放一个数据元素
    struct lNode *next;         // 指针指向下一个元素
}lNode, *linkList;              // lNode 强调是一个结点,linkList 强调是一个链表

带头结点单链表和不带头结点单链表关系
在这里插入图片描述
1、带头结点,写代码更方便

2、不带头结点,写代码更麻烦,对第一个结点需要进行特殊处理

三、操作

int initList(linkList *l);                      // 初始单链表
int destroyList(linkList *l);                   // 销毁单链表
int listInsert(linkList *l,int i,ElemType e);   // 按位序插入
int listDelete(linkList *l,int i,ElemType *e);  // 按位序删除
lNode* locateElem(linkList l,ElemType e);       // 按值查找
lNode* getElem(linkList l,int i);               // 按位查找
int printfList(linkList l);                     // 打印链表
int insertNextNode(lNode *p,ElemType e);        // 按结点后插
int insertPriorNode(lNode *p,ElemType e);       // 按结点前插
int tailInsert(linkList l,ElemType e);          // 链表尾插
int headInsert(linkList l,ElemType e);          // 链表头插

1、InitList(&L)

初始化单链表

<!- 不带头结点 ->

/****************************************
* l - 要操作的顺序表首地址(l是指针类型)
* 备注:不带头结点初始化
****************************************/
int initList(linkList *l)
{
    *l = NULL;
    return 0;
}

在这里插入图片描述
<!- 带头节点 ->

/****************************************
* l - 要操作的顺序表首地址(l是指针类型)
* 备注:带头结点初始化
****************************************/
int initList(linkList *l)
{
    *l = (lNode*)malloc(sizeof(lNode));     // 为头结点分配空间
    if(*l == NULL) return 1;                // malloc 调用失败
    (*l)->next = NULL;                      // 初始化头结点中 next 变量
    (*l)->data = 0;                         // 初始化头结点中 data 变量

    return 0;
}

在这里插入图片描述

2、DestroyList(&L)

销毁单链表。这步必须要有,否则会造成内存泄漏。

/****************************************
* l - 要操作的顺序表首地址(l是指针类型)
* 备注:带头结点初始化
****************************************/
int destroyList(linkList *l)
{
    lNode *d = *l;                          // d 指向要释放的结点(选择释放结点)
    while(d != NULL){                       // 判读结点是否全部释放
        (*l) = d->next;                     // 调整列表(去除释放结点后的链表)
//      printf("d->data = %d\n",d->data);   // 做调试使用,查看是否能全部销毁
        free(d);                            // 释放链表
        d = *l;                             // 提取下一个释放结点
    }

    return 0;
}

在这里插入图片描述

3、ListInsert(&L,i,e)

按位序插入。在表 L 中的第 i 个位置插入指定元素 e。

设计思路:找到第 i-1 个节点,将新节点插入其后。

/****************************************
* l - 要操作的顺序表首地址(l是指针类型)
* i - 要插入的位置
* e - 新结点存储的数据
* 备注:带头结点初始化
****************************************/
int listInsert(linkList *l,int i,ElemType e)
{
    if(i<1) return 1;                           // i 值不合法
    lNode *p = NULL;                            // 指针 p 指向当前扫描的结点
    int j = 0;                                  // 当前 p 指向是第几个结点
    p = *l;                                     // *l 指向头结点,头结点是第 0 个结点,不存在数据
    while(p!=NULL && j<i-1){                    // p!=NULL 确保在链表有效范围内
        p = p->next;
        j++;
    }
    if (p == NULL) return 1;                    // i 值不合法
    lNode *s = (lNode*)malloc(sizeof(lNode));   // 创建新结点
    s->data = e;                                // 给新结点赋值
    s->next = p->next;
    p->next = s;
    /* 以上三行赋值不能颠倒,否则会出现链表断裂情况 */

    return 0;
}

在这里插入图片描述

4、ListDelete(&L,i,&e)

按位序删除。删除表 L 中第 i 个位置的元素,并用 e 返回删除元素的值。

设计思路:找到第 i-1 个节点,释放第 i 个结点。

/****************************************
* l - 要操作的顺序表首地址(l是指针类型)
* i - 要删除的位置
* e - 返回删除结点数据
* 备注:带头结点初始化
****************************************/
int listDelete(linkList *l,int i,ElemType *e)
{
    if(i < 1) return 1;                     // i 值不合法
    lNode *p = NULL;                        // p 指向当前扫描的结点
    int j = 0;                              // 当前 p 指向的第几个结点
    p = *l;                                 // p 指向头结点,头结点为第 0 个结点,不存放数据
    while(p!=NULL && j<i-1){                // p!=NULL - 确保在单链表有效范围内,j<i-1 - 寻找 i-1 个结点
        p = p->next;
        j++;
    }
    if(p == NULL) return 1;                 // i 值不合法
    if(p->next == NULL) return 1;           // 第 i-1 个结点之后无其他结点(处理最后一个结点)
    lNode *s = p->next;                     // s 指向被删除结点
    (*e) = p->data;                         // 用 e 返回元素的值
    p->next = s->next;                      // 调整链表链接关系
    free(s);                                // 释放结点

    return 0;
}

在这里插入图片描述

5、LocateElem(L,e)

按值查找操作。在表 L 中查找具有给定关键字值的元素。

/****************************************
* l - 要操作的顺序表首地址(l是指针类型)
* e - 要查找的数据
* 备注:带头结点初始化
****************************************/
lNode* locateElem(linkList l,ElemType e)
{
    lNode *p = l->next;
    while(p!=NULL && p->data!=e){   // 从第 1 个结点开始查找数据域为 e 的结点
        p=p->next;
    }

    return p;                       // 返回第一个数据为 e 的结点指针,否则返回 NULL
}

6、GetElem(L,i)

按位查找。返回第 i 个元素。

/****************************************
* l - 要操作的顺序表首地址(l是指针类型)
* i - 要查找的位序
* 备注:带头结点初始化
* 备注:如果 i=0,返回头结点地址,如果 i 超过链表长度,返回NULL
****************************************/
lNode* getElem(linkList l,int i)
{
    if(i<0) return NULL;            // i 值不在合法范围
    lNode *p = NULL;                // p 指向当前扫描的结点
    int j = 0;                      // 表示 p 当前指向第几个结点
    p = l;                          // 将 p 值指向头结点(相当于第0个结点,不存在数据)
    while(p!=NULL && j<i){
        p=p->next;
        j++;
    }
    return p;
}

7、Length(L)

获取链表长度

<!- 可以在头结点中存放链表长度(会根据不同数据类型有所限制) ->

未实现

8、PrintfList(L)

/****************************************
* l - 要操作的顺序表首地址(l是指针类型)
* 备注:带头结点初始化
****************************************/
int printfList(linkList l)
{
    lNode *p = l;
    while(p != NULL){
        printf("p->data = %d\n",p->data);       // 不同类型,打印语句不同
        p = p->next;
    }

    return 0;
}

9、InsertNextNode(p,e)

按结点后插。在结点 p 后插入元素 e。

/****************************************
* p - 链表中某一结点指针
* e - 插入结点中数据
* 备注:带头结点初始化
****************************************/
int insertNextNode(lNode *p,ElemType e)
{
    if(p==NULL) return 1;                       // p 不合法
    lNode *s = (lNode*)malloc(sizeof(lNode));   // 分配内存
    if(s==NULL) return 1;                       // 内存分配失败
    s->data = e;                                // s 指向结点保存数据 e
    s->next = p->next;                          // 调整链表
    p->next = s;
    return 0;
}

在这里插入图片描述

10、InsertPriorNode(p,e)

按结点前插。在 p 结点之前查找元素 e。

问题:单链表只能向后查找,如何找到 p 前驱结点。

解决方法

1、传入头指针,找到结点 p 的前驱结点 q(InsertPriorNode(l, p, e))。

2、进行按结点后插操作,插入后移动数据

/****************************************
* p - 链表中某一结点指针
* e - 插入结点中数据
* 备注:带头结点初始化
****************************************/
int insertPriorNode(lNode *p,ElemType e)
{
    if(p==NULL) return 1;                       // p 不合法
    lNode *s = (lNode*)malloc(sizeof(lNode));   // 分配内存
    if(s==NULL) return 1;                       // 内存分配失败
    s->next = p->next;                          // 调整链表
    p->next = s;
    s->data = p->data;                          // 移动数据
    p->data = e;
    return 0;
}

在这里插入图片描述

11、tailInsert(l,e)

链表后插操作。主要用于单链表创建

链表后插操作单链表建立流程:

1、初始化一个单链表。

2、每次取一个数据插入到表尾。

/****************************************
* l - 要操作的顺序表首地址(l是指针类型)
* e - 要插入的数据
* 备注:带头结点初始化
* 缺点:每插入一个数据,都需要遍历一次链表(时间复杂度高)
****************************************/
int tailInsert(linkList l,ElemType e)
{
    // 1、遍历单链表,找到最后一个结点
    lNode *s = l;
    while(s->next!=NULL){
        s = s->next;
    }
    // 2、使用结点后插操作进行插入结点
    return insertNextNode(s,e);
}

12、headInsert(l,e)

链表头插操作。主要用于单链表创建

链表头插操作单链表建立流程:

1、初始化一个单链表。

2、每次取一个数据插入到表头。

/****************************************
* l - 要操作的顺序表首地址(l是指针类型)
* e - 要插入的数据
* 备注:带头结点初始化
****************************************/
int headInsert(linkList l,ElemType e)
{
    // 对链表头结点使用结点后插操作
    return insertNextNode(l,e);
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值