数据结构之链表对数据的运算及特征

 思路:对链表的编写采用自定义头文件,用【#ifndef  #define  #endif】这组定义头文件,其次,我们约定头结点没有数据(约定大于技术)。

头文件引用:<>指定根目录         " "当前目录或指定目录

头文件中的 #ifndef/#define/#endif 防止该头文件被重复引用
其实“被重复引用”是指一个头文件在同一个cpp文件中被include了多次,这种错误常常是由于include嵌套造成的。
预处理阶段
比如:存在a.h文件
#include "b.h"而此时b.cpp文件导入了
#include "a.h" 和#include "b.h"此时就会造成c.b重复引用。
下面给一个#ifndef/#define/#endif的格式:
#ifndef A_H意思是"if not define a.h"  如果不存在a.h
接着的语句应该#define A_H  就引入a.h
最后一句应该写#endif   否则不需要引入

此段原文链接:#ifndef/#define/#endif使用详解_叶子一哥的博客-CSDN博客_ifdefine

root@ubuntu:/mnt/U_share/DATA/linklist# mkdir inc
root@ubuntu:/mnt/U_share/DATA/linklist# mkdir src
root@ubuntu:/mnt/U_share/DATA/linklist# vim inc/linklist.h
root@ubuntu:/mnt/U_share/DATA/linklist# vim src/linklist.c

单链表:_ LINKLIST_H_:

#ifndef _LINKLIST_H_
#define _LINKLIST_H_
#include <stdio.h>
#include <stdlib.h>
typedef int type;
typedef struct node{
    type data;
    struct node *pnext;
    //pLinklist pnext;是错的,机器思想(一步一步)
}Linklist,*pLinklist;  
//初始化链表
int list_init(pLinklist *L);
//链表判空
int list_empty(pLinklist L);
//链表头插法
int list_insert_head(pLinklist L,type data);
//删除指定值所在结点
int list_delete_data(pLinklist L,type data);
//查询链表元素
pLinklist list_select_data(pLinklist L,type data);
//链表元素展现
int list_show(pLinklist L);
//释放链表 
int list_destory(pLinklist *L);
//递归展现
int list_show_back(pLinklist l);
#endif

单链表:Linklist.c:

#include "linklist.h"
//初始化链表
int list_init(pLinklist *L)
{
    *L = malloc(sizeof(Linklist));
    if(NULL == *L){
        perror("L_malloc");
        return -1;
    }
    (*L)->pnext = NULL;
    return 0;
}
//链表判空
int list_empty(pLinklist l)
{
    if(NULL==l)
        return -1;
    if(NULL == l->pnext)
        return 0;
    return -1;
}
//链表头插法
int list_insert_head(pLinklist l,type data)
{
    if(NULL==l)
        return -1;
    //创建新的结点存储数据 
    pLinklist p = malloc(sizeof(Linklist));
    if(NULL==p){
        perror("data_malloc");
        return -1;
    }
    p->data = data;
    //新结点的pnext指向原来头结点的下一个结点 
    p->pnext = l->pnext;
    //头结点的pnext指向插入的结点
    l->pnext = p;
    return 0;
}
//删除指定值所在结点
int list_delete_data(pLinklist l,type data)
{
    if(NULL==l || 0==list_empty(l))
        return -1;
    //标记要删除的结点 
    pLinklist p;
    while(l->pnext){//隐含l->pnext != NULL
        if(l->pnext->data == data){
            p = l->pnext;
    //待删除结点p的前一个结点指向其后一个结点 
            l->pnext = l->pnext->pnext;
            free(p);
        }
        l = l->pnext;//结点后移 
    }
    return 0;
}
//查询链表元素
pLinklist list_select_data(pLinklist l,type data)
{
    if(NULL==l || 0==list_empty(l))
        return NULL;
    while(l->pnext){
        //约定头结点不存放数据
        if(data == l->pnext->data)
            return l->pnext;
        l = l->pnext;
    }
    return NULL;
}
//链表元素展现
int list_show(pLinklist l)
{
    if(NULL==l || 0==list_empty(l))
        return -1;
    while(l->pnext){
        printf("%d ",l->pnext->data);
        l = l->pnext;
    }
    puts("");
    return 0;
}
//释放链表 
int list_destory(pLinklist *L)
{
    if(NULL==*L)
        return -1;
#if 0
    //标记头结点,从头结点开始释放 
    pLinklist p;
    while(*L){
        p = *L;
        *L = (*L)->pnext;//头结点后移
        free(p);//释放原来的头结点
    }//循环结束后其值为NULL
#else
    //先删除数据结点,再删除头结点
    pLinklist p = (*L)->pnext,q;
    while(p){
        q = p;
        p = p->pnext;
        free(q);
    }
    free(*L);
    *L = NULL;
    return 0;
}
#endif
//递归
int list_show_back(pLinklist l)
{
    if(NULL==l)
        return -1;
    list_show_back(l->pnext);
    printf("%d ",l->data);
}

单链表:main.c

#include "linklist.h"
int list_init(pLinklist *L);
int list_empty(pLinklist l);
int list_insert_head(pLinklist l, type data);
int list_delete_data(pLinklist l, type data);
pLinklist list_select_data(pLinklist l,type data);
int list_show(pLinklist l);
int list_destory(pLinklist *L);
int list_show_back(pLinklist l);
int main(int argc, char *argv[])
{ 
    pLinklist p = NULL;
    type data;
    list_init(&p);
    while(1){
        printf("please input data:");
        if(0==scanf("%d",&data)){
            getchar();
            break;
        }
        list_insert_head(p,data);
    }
    list_show(p);
    while(1){
        printf("please delete data:");
        if(0==scanf("%d",&data)){
            getchar();
            break;
        }
        list_delete_data(p,data);
    }
    //递归show是反过来的
    list_show_back(p);
    puts("");
    list_show(p);
    fputs("please show data:",stdout);
    scanf("%d",&data);
    printf("%p",list_select_data(p,data));
    list_destory(&p);
    if(NULL == p){
        fputs("\nthis is NULL!\n",stdout);
    }
    return 0;
} 

makefile:

all:
	gcc ./src/*.c -o link -I ./inc/
clean:
	rm link

单链表结果:

root@ubuntu:/mnt/U_share/DATA/linklist# make
gcc ./src/*.c -o link -I ./inc/
root@ubuntu:/mnt/U_share/DATA/linklist# ls
inc  link  makefile  src
root@ubuntu:/mnt/U_share/DATA/linklist# ./link
please input data:1
please input data:1
please input data:2
please input data:3
please input data:3
please input data:3
please input data:q
3 3 3 2 1 1 
please delete data:1
please delete data:q
1 2 3 3 3 0 //这里最后是一个0,是因为第一个头结点是NULL,没存数据(约定)
3 3 3 2 1 
please show data:2
0x558b4eb7bb20    //返回的是2的地址
this is NULL!

====================================================================

总结:编程少写跳转,goto,break,continue,极度灵活,一旦掌握不好就会出现bug,另外,机器会预取址(下两个),影响性能
返回首地址要记录,strcpy();
删除值,要考虑尾节点和连续值
链表,指定位置插入(是否合法,>0或者<max,可能段错误)
空指针无法被访问
结构体记录,可以没必要,易出bug,全局变量(改),影响性能,
单独定义一个结构体,只记录另一个结构体的头节点
用返回值或者指针编写函数,返回值弊端是返回的值为-1
可返回局部变量的地址,不可返回局部变量,即改形参对实参无影响
多个写递归,找到结点后将该结点的地址传进去,继续递归
链表是离散的,不能当作顺序表来处理,不能++或者--,需要定义指针进行指向

释放链表有两种方法①直接从头结点释放②先释放数据,在释放结点
-I 链接指定头文件
段错误:非法访问内存(本质)
递归是存放进栈区的,,不好把控,时间复杂度也高,少用递归,进程结束后自动释放内存
程序结束会自动释放内存,那么为什么要释放,即destory()呢?

这是为了程序很大时,内存泄漏可能导致内存不够。

 循环链表:(待改进)

头文件:_LOOPLIST_H_

#ifndef _LOOPLIST_H_
#define _LOOPLIST_H_
#include <stdio.h>
#include <stdlib.h>

typedef int type;
typedef struct node{
    type data;
    struct node *next;
}Looplist,*pLooplist;
//循环链表初始化
int list_init(pLooplist *L);
//循环链表判空
int list_empty(pLooplist l);
//循环链表增加结点、尾插
int list_insert_head(pLooplist l,type data);
//循环链表删除指定值所在节点
int list_delete_data(pLooplist l,type data);
//循环链表查询
Plooplist list_select_data(pLooplist l, type data);
//循环链表show
int list_show(pLooplist l);
//循环链表destory
int list_free(pLooplist *L);
//循环链表递归show
int list_show_back(pLooplist phead, pLooplist l);
#endif

外部函数文件:Looplist.c

#include "clooplist.h"

//循环链表初始化
int list_init(pLooplist *L)
{
    *L = malloc(sizeof(Looplist));
    if(NULL == *L){
        perror("init_malloc");
        return-1;
    }
    (*L)->next = *L;
    return 0;
}
//循环链表判空
int list_empty(pLooplist l)
{
    if(NULL == l)
        return -1;
    if(l->next == l)
        return 0;
    return -1;
}
//循环链表增加结点、尾插
int list_insert_tail(pLooplist l,type data)
{
    if(NULL == l)
        return -1;
    //头结点定义为 l
    pLooplist phead = l;
    //因为是尾插,所以要先遍历到末尾
    while(l->next != phead){
        l = l->next;
    }
    pLooplist p = malloc(sizeof(Looplist));
    if(NULL == p){
        perror("data_malloc");
        return -1;
    }
    p->data = data;
//这种顺序相对而言更好点,先指再断_指,可思考互换
    p->next = phead;
    l->next = p;
    return 0;
}
//循环链表删除指定值所在节点
int list_delete_data(pLooplist l,type data)
{
    if(NULL==l || 0==list_empty(l)){
        perror("s");
        return -1;
    }
    pLooplist phead = l,p = NULL;
    while(l->next != l){
        if(l->next->data == data){
            p = l->next;
            l->next = p->next;
            free(p);
            //暂停此循环是为了删除连续的相同结点
            continue;
        }
        l = l->next;
    }
    return 0;
}
//循环链表查询
pLooplist list_select_data(pLooplist l, type data)
{
    if(NULL==l || 0==list_empty(l))
        return NULL;
    pLooplist phead = l;
    while(l->next != phead){
        if(data == l->next->data)
            return l->next;
        l = l->next;
    }
    return NULL;
}
//循环链表show
int list_show(pLooplist l)
{
    if(NULL == l || 0==list_empty(l))
        return -1;
    pLooplist phead = l;
    while(l->next != phead){
        printf("%d ",l->next->data);
        l = l->next;
    }
    return 0;
}
//循环链表destory
int list_destory(pLooplist *L)
{
    if(NULL == *L)
        return -1;
    pLooplist phead = *L,p;
    //先释放数据结点,再释放头结点
    phead = phead->next;
    while(phead != *L){
        p = phead;
        phead = phead->next;
        free(p);
    }//和上列循环类似,只是写法不一样
    free(*L);
    *L = NULL;
    return 0;
}
//循环链表递归show
int list_show_back(pLooplist phead, pLooplist l)
{
    if(phead == l)//前提是phead = l(已经) 
        return 0;
    list_show_back(phead,l->next);
    printf("%d ",l->data);
}

主函数:main.c

#include "clooplist.h"
int list_init(pLooplist *L);
int list_empty(pLooplist l);
int list_insert_tail(pLooplist l,type data);
int list_delete_data(pLooplist l,type data);
pLooplist list_select_data(pLooplist l, type data);
int list_show(pLooplist l);
int list_destory(pLooplist *L);
int list_show_back(pLooplist phead, pLooplist l);
int main(int argc, char *argv[])
{ 
    pLooplist p = NULL;
    list_init(&p);//初始化

    type data;
    while(1){
        printf("please input data : ");
        if(0 == scanf("%d",&data)){
            getchar();
            break;
        }
        list_insert_tail(p,data);//尾插 
    }
    list_show_back(p,p->next);//递归show
    puts("");
    list_show(p);//show
    puts("");
    type i;
    while(1){
        printf("please delete data : ");
        if(0 == scanf("%d",&i)){
            getchar();
            break;
        }
        list_delete_data(p,i);//删除
    }
    list_show(p);
    printf("please input select:");
    scanf("%d",&data);
    printf("the select:%p\n",list_select_data(p,data));
    list_destory(&p);
    if(NULL == p)
        fputs("this is NULL\n",stdout);
    return 0;
} 

结果:

please input data : 1
please input data : 2
please input data : 3
please input data : 3
please input data : 4
please input data : q
4 3 3 2 1 
1 2 3 3 4 
please delete data : 2

总结:

scanf(),返回值个数

尝试删除尾节点,NULL已经被赋,尾结点要分类讨论

3 2 1 1 1 删1 前移+后移,continue

一个主函数中两个while(1)?

双向循环链表: 

头文件:_DLOOPLIST_H_

#ifndef _DLOOPLIST_H_
#define _DLOOPLIST_H_
#include <stdio.h>
#include <stdlib.h>
//C 语言标准(C89) 没有定义布尔类型,该头文件宏定义包含 true 和 false
#include <stdbool.h>
typedef int type;
typedef struct node{
    type data;//自定义型的数据
    struct node *pb, *pn;//前、后指针
}Dlooplist, *pDlooplist;

//返回值初始化双向循环链表
pDlooplist list_create();
//返回值为布尔类型的双向循环链表判空
bool list_empty(pDlooplist l);
//双向循环链表尾插数据函数
bool list_insert_tail(pDlooplist l, type data);
//删除函数
bool list_delete_data(pDlooplist l, type data);
//与指定值相等的节点全部修改
bool list_change(pDlooplist l, type val, type data);
//返回地址的双向链表查询函数
//第三参数是二级指针,负责返回查询到节点的地址
bool list_select_data(pDlooplist l, type data, pDlooplist *Node);
//正序show
bool list_show_seq(pDlooplist l);
//逆序show
bool list_show_back(pDlooplist l);
//释放函数
bool list_free(pDlooplist *L);

#endif 

外部函数文件:dlooplist.c

#include "dlooplist.h"

//返回值初始化双向循环链表
pDlooplist list_create()
{
    pDlooplist p = malloc(sizeof(Dlooplist));
    if(NULL == p){
        perror("list_malloc");
        return NULL;
    }
    //前、后结点都指向自己 
    p->pb = p->pn = p;
    return p;
}
//返回值为布尔类型的双向循环链表判空
bool list_empty(pDlooplist l)
{
    if(NULL == l)
        return false;
    if((l==l->pb) && (l==l->pn))
        return true;
    return false;
}
//双向循环链表尾插数据函数
bool list_insert_tail(pDlooplist l, type data)
{
    if(NULL == l)
        return false;
    pDlooplist p = malloc(sizeof(Dlooplist));
    if(NULL == p){
        perror("data_malloc");
        return false;
    }
    p->data = data;
    p->pb = l->pb;//p->pb = l->pn;
    p->pn = l;//p->pn = l->pb;
    //让原来的尾结点的 pn 指向新的尾结点
    l->pb->pn = p;//l->pn = p;
    //让头结点的 pb 指向新的尾结点
    l->pb = p;//l->pb = p;
    return true;
}
//逆序删除函数
bool list_delete_data(pDlooplist l, type data)
{
    if(NULL==l || list_empty(l))
        return false;//隐含list_empty(l)==true
    pDlooplist phead = l,p;
    while(l->pb != phead){//or l->pn != phead
        if(data == l->pb->data){//判断相应改变 
            p = l->pb;
            l->pb = l->pb->pb;
            p->pb->pn = l;
            free(p);
            continue;
        }
        l = l->pb;
    }
    return true;
}
//与指定值相等的节点全部修改,val:结果 data:目标
bool list_change(pDlooplist l, type val, type data)
{
    if(NULL == l || list_empty(l))
        return false;
    pDlooplist phead = l;
    while(l->pn != phead){
        if(data == l->pn->data)
            l->pn->data = val;
        l = l->pn;
    }
    return true;
}
//返回地址的双向链表查询函数
//第三参数是二级指针,负责返回查询到节点的地址
bool list_select_data(pDlooplist l, type data, pDlooplist *Node)
{
    if(NULL == l || list_empty(l)){
        *Node = NULL;
        return false;
    }
    pDlooplist phead = l;
    while(l->pn != phead){
        if(data == l->pn->data){
            *Node = l->pn;
            return true;
        }
        l = l->pn;
    }
    *Node = NULL;
    return true;
}
//正序show
bool list_show_seq(pDlooplist l)
{
    if(NULL==l || list_empty(l))
        return -1;
    pDlooplist phead = l;
    while(l->pn != phead){
        printf("%d ",l->pn->data);
        l = l->pn;
    }
    puts("");
    return true;
}
//逆序show
bool list_show_back(pDlooplist l)
{
    if(NULL==l || list_empty(l))
        return -1;
    pDlooplist phead = l;
    while(l->pb != phead){
        printf("%d ",l->pb->data);
        l = l->pb;
    }
    puts("");
    return true;
}
//释放函数
bool list_destory(pDlooplist *L)
{
    if(NULL == *L)
        return false;
    pDlooplist p = (*L)->pn;
    while(p != *L){
        p = p->pn;
        free(p->pb);
    }
    free(*L);
    *L = NULL;
    return true;
}

主函数: main.c

#include "dlooplist.h"

pDlooplist list_create();
bool list_empty(pDlooplist l);
bool list_insert_tail(pDlooplist l, type data);
bool list_delete_data(pDlooplist l, type data);
bool list_change(pDlooplist l,type val,type data);
bool list_select_data(pDlooplist l,type data,pDlooplist *Node);
bool list_show_seq(pDlooplist l);
bool list_show_back(pDlooplist l);
bool list_destory(pDlooplist *L);
int main(int argc, char *argv[])
{
    pDlooplist p = list_create(),q=NULL;
    if(NULL == p)//指针 p 未定义成功
        return -1;
    type data;
    while(1){
        printf("please input data : ");
        if(0 == scanf("%d",&data)){
            getchar();
            break;
        }
        list_insert_tail(p,data);//尾插
    }
    list_show_back(p);//逆序show
    list_show_seq(p);//正序show
    while(1){
        printf("please delete data : ");
        if(0 == scanf("%d",&data)){
            getchar();
            break;
        }
        list_delete_data(p,data);//删除
    }
    list_show_seq(p);
    printf("please input select:");
    scanf("%d",&data);
    printf("the select:%p\n",list_select_data(p,data,&q));
    //将值 3 全部改为 2
    list_change(p,2,3);
    list_show_seq(p);
    list_destory(&p);
    if(NULL == p)
        fputs("this is NULL\n",stdout);
    return 0;
} 

结果:

root@ubuntu:/mnt/U_share/DATA/linklist/linklist_duplexing# ./link
please input data : 1
please input data : 1
please input data : 2
please input data : 2
please input data : 3
please input data : 3
please input data : 4
please input data : 5
please input data : 6
please input data : q
6 5 4 3 3 2 2 1 1 
1 1 2 2 3 3 4 5 6 
please delete data : 1
please delete data : q
2 2 3 3 4 5 6 
please input select:4
the select:0x1
2 2 2 2 4 5 6 
this is NULL

总结:指针 = 结点
双向链表:更改四个指针,至少四行代码
根据需求而定,编写代码时要时刻注意指针的指向,画图更好理解
*node = null;防止成为野指针

链表:一种物理存储单元上非连续、非顺序的存储结构 , 数据元素的逻辑顺序是通过链表中的 指针 链接次序实现的。

链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。

每个结点包括两个部分:存储 数据元素的数据域,存储下一个结点地址的指针域。

相比于线性表顺序结构而言,操作复杂,但由于不用必须按顺序存储,链表在插入的时候可以达到O (1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O (n)的时间,而线性表和顺序表相应的时间复杂度分别是O (logn)和O (1)。

优点:使用链表结构可以克服 数组 链表需要预先知道数据大小的缺点,也可充分利用计算机内存空间,实现灵活的内存动态管理。

====================================================================

顺序存储的特点:
逻辑顺序与物理顺序一致,本质上是用内存中一片连续的空间构建成数组进行存储;

存储密度大,存储空间利用率相对而言更高
链式存储的特点:
灵活,时间复杂度较小,用元素结点的指向进行相互关联,进而构成一条链表,其插入、删除结点不需要移动其余节点,只改变指向即可
静态存储的特点:
固定内存,在程序运行的过程中不考虑动态分配以及追加内存的问题
动态存储的特点:
动态分配内存,也可动态增加内存,能有效的利用内存资源,使程序具有良好的延展性

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值