链表相关知识

单链表

typedef struct LNode{
    
    
    int data;  //结点的数据域
    struct LNode *next;  //结点的指针域
}LNode,*LinkList;  //LinkList为指向结构体的指针类型
 
   LNode *L; //声明一个指向单链表第一个结点的指针:LinkList L;  //声明一个指向单链表第一个结点的指针(代码可读性更强)

LinkListLNode*两者本质上是等价的,习惯上用LinkList定义单链表,强调定义的是某个单链表的头指针;用LNode*定义指向单链表中任意结点的指针变量

初始化

不带头结点的单链表

头指针指向的结点就存放数据

#include<stdio.h>
#include<stdlib.h>
//由于C语言中没有布尔类型,因此我们可以这样替换
typedef int bool;
#define true 1
#define false 0

typedef struct LNode{
    
    
    int data;  //结点的数据域
    struct LNode *next;  //结点的指针域
}LNode,*LinkList;  //LinkList为指向结构体的指针类型

//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
    
    
    L = NULL;
    return true;
}
//判断单链表是否为空
bool Empty(LinkList *L){
    
    
    if(L == NULL)
        return true;
    else
        return false;
}

int main(){
    
    
    LinkList L; //声明一个指向单链表的指针
    InitList(&L); //初始化一个空表
   //LNode *p = (LNode*)malloc(sizeof(LNode));
    return 0;
}

带头结点的单链表

生成新节点作为头结点,头指针指向头结点,头节点不存放数据

#include<stdio.h>
#include<stdlib.h>
//由于C语言中没有布尔类型,因此我们可以这样替换
typedef int bool;
#define true 1
#define false 0

typedef struct LNode{
    
    
    int data;  //结点的数据域
    struct LNode *next;  //结点的指针域
}LNode,*LinkList;  //LinkList为指向结构体的指针类型

//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
    
    
    L = (LNode *)malloc(sizeof(LNode)); //分配一个头节点
    if(L == NULL)  //内存不足,分配失败
        return false;
    L->next = NULL;  //头节点之后暂时还没有结点
    return true;
}
//判断单链表是否为空
bool Empty(LinkList *L){
    
    
    if(L->next == NULL)
        return true;
    else
        return false;
}

int main(){
    
    
    LinkList L; //声明一个指向单链表的指针
    InitList(&L); //初始化一个空表
    return 0;
}

插入

按位序插入

带头结点

bool ListInsert(LinkList *L,int i,int e){
    
    
    if(i < 1)
        return false;
    LNode *p;  //指针p指向当前扫描的结点
    int j = 0;  //当前p指向的是第几个结点(初始指向第0个结点即头结点)
    p = L;
    while(p != NULL && j < i-1){
    
    
        p = p->next;
        j++;
    }
    if(p==NULL)  //i值不合法
        return false;
    //新建一个结点然后插入L中
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;
    p->next = s;  //将结点s连到p之后
    return true;  //插入成功
}

不带头结点

bool ListInsert(LinkList *L,int i,int e){
    
    
    if(i < 1)
        return false;
     //只有这里插入在首位时
    if(i == 1){
    
    
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = e;
        s->next = L;
        L = s;
        return true;
    }
    LNode *p;  //指针p指向当前扫描的结点
    int j = 1;  //当前p指向的是第几个结点(初始指向第0个结点即头结点)
    p = L;
    while(p != NULL && j < i-1){
    
    
        p = p->next;
        j++;
    }
    if(p==NULL)  //i值不合法
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;
    p->next = s;  //将结点s连到p之后
    return true;  //插入成功
}

指定结点的后插操作

bool InsertNextNode(LNode *p,int e){
    
    
    if(p == NULL)
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if(s == NULL) // 内存分配失败
        return false;
    s->data = e;   //用新结点保存元素e
    s->next = p->next;
    p->next = s;  //将结点s连到p之后
    return true;
}

指定结点的前插

通过指定结点只能找到后继的结点,不能逆向往前找,要想在指定结点前面插入,可以再传入链表头指针,或者通过下面这种比较巧妙的方法,在指定结点之后先插入一个结点,然后再交换两个结点的值,就相当于在指定节点前插入

bool InsertPriorNode(LNode *p,int e){
    
    
    if(p == NULL)
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if(s == NULL)
        return false;
    s->next = p->next;  
    p->next = s;       //将新结点s连接到p之后
    s->data = p->data;  //将p中元素复制到s中
    p->data = e;     //p中元素覆盖为e
    return true;
}

删除(带头结点)

按位序删除

bool ListDelete(LinkList *L,int i){
    
    
    if(i < 1)
        return false;
    LNode *p;  //指针p指向当前扫描的结点
    int j = 0;  //当前p指向的是第几个结点(初始指向第0个结点即头结点)
    p = L;
    while(p != NULL && j < i-1){
    
    
        p = p->next;
        j++;
    }
    if(p==NULL)  //i值不合法
        return false;
    if(p->next == NULL)  
        return false;
    LNode *q = p->next;  //令q指向被删除结点
    int e = q->data;  //用e返回元素的值,可用作返回使用
    p->next = q->next;  //将*q结点从链中断开
    free(q);  //释放结点的存储空间
    return true;  //删除成功
}

指定结点的删除

删除结点时需要将删除结点的前驱和后继结点连接,但我们得知前驱结点,因此,因此我们将需要删除的结点p与第p+2个结点连接(删除第p+1个结点),再将第p+1个结点的值赋给第p个结点,这样就相当于删除了第p个结点。

bool DeleteNode(LNode *p){
    
    
    if(p==NULL)
        return false;
    LNode *q = p->next;  //令q指向*p的后继节点
    p->data = p->next->data;  //和后继结点交换数据域
    p->next = q->next;  //将*p结点从链中断开
    free(q);
    return true;
}

这种写法存在bug,如果要删除的结点为最后一个结点就会出现问题

查找(带头结点)

按位查找

LNode * GetElem(LinkList L,int i){
    
    
    if(i < 0)
        return NULL;
    LNode *p;
    int j = 0;
    p = L;
    while(p != NULL && j < i){
    
    
        p = p->next;
        j++;
    }
    return p;
}

按值查找

LNode * LocateElem(LinkList L,int e){
    
    
    LNode *p = L->next;  //查找操作从第一个结点开始
    while(p != NULL && p->data != e){
    
    
        p = p->next;
    }
    return p;  //找到返回结点指针,否则返回NULL
}

求单链表长度

int Length(LinkList L){
    
    
    int len = 0;
    LNode *p = L;
    while(p->next != NULL){
    
    
        p = p->next;
        len++;
    }
    return len;
}

单链表的建立

尾插法建立单链表

可以利用上面提到的按位插入操作,插入位置每次都是表尾,这种方式每次插入时都要从表头位置遍历到表尾位置,时间复杂度为O(n2)
我们还可以设置一个尾指针,初始时指向头结点,每生成一个新结点插入尾指针之后,将尾指针后移,再次移到尾部

//n为链表长度
void CreateList_R(LinkList *L,int n){
    
    
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    r = L;
    for(int i = 0; i < n; i++){
    
    
        p = (LNode *)malloc(sizeof(LNode));  //创建一个新结点
        scanf("%d",&(p->data));  //输入新结点数据域的值
        p->next = NULL;   //将结点指针域指向NULL
        r->next = p;  //将结点连接到链表上
        r = p;    //将r指向链表尾部
    }
}

前插法建立单链表

void CreateList_H(LinkList *L,int n){
    
    
    L = (LinkList)malloc(sizeof(LNode));  
    L->next = NULL;
    for(int i = 0; i < n; i++){
    
    
        p = (LNode *)malloc(sizeof(LNode));  //创建一个新结点
        scanf("%d",(&p->data));  //输入新结点数据域的值
        p->next = L->next;   //将新结点放在第一个第一个结点之前
        L->next = p;   //将头结点的指针指向新结点
    }
}

双链表

typedef struct DNode{
    
    
    int data;  //结点的数据域
    struct DNode *prior;  //指向结点的直接前驱
    struct DNode *next;  //指向结点的直接后继
}DNode,*DLinkList;  //LinkList为指向结构体的指针类型

初始化

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

typedef int bool;
#define true 1
#define false 0

typedef struct DNode{
    
    
    int data;  //结点的数据域
    struct DNode *prior;  //指向结点的直接前驱
    struct DNode *next;  //指向结点的直接后继
}DNode,*DLinkList;  //LinkList为指向结构体的指针类型

bool InitDLinkList(DLinkList *L){
    
    
    L = (DNode *)malloc(sizeof(DNode));  //分配一个头结点
    if(L == NULL)
        return false;
    L->prior = NULL;  //头结点的prior永远指向NULL
    L->next = NULL;  //头结点之后暂时没有结点
    return true;
}

//判断双链表是否为空
bool Empty(DLinkList L){
    
    
    if(L->next == NULL)
        return true;
    else
        return false;
}


int main(){
    
    
    //初始化双链表
    DLinkList L;
    InitDLinkList(L);
    return 0;
}

插入

后插操作

//在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s){
    
    
    if(p == NULL || s == NULL)
        return false;
    s->next = p->next;
    if(p->next != NULL)
        p->next->prior = s;
    s->prior = p;
    p->next = s;
    return true;
}

前插操作

在结点p之前插入结点可以转化为结点p前一个结点的后插操作

删除

//删除结点p的后继结点
bool DeleteNextDNode(DNode *p){
    
    
    if(p == NULL)
        return false;
    DNode *q = p->next;
    if(q == NULL)
        return false;
    p->next = q->next;
    if(q->next != NULL)
        q->next->prior = p;
    free(q);
    return true;
}

void DestoryList(DLinkList *L){
    
    
    //循环释放各个数据结点
    while(L->next != NULL)
        DeleteNextDNode(L);
    free(L);  //释放头结点
    L = NULL;  //头指针指向NULL
}

遍历

    //后向遍历
    while(p != NULL){
    
    
            //对结点p做相应处理,如打印
        p = p->next;
    }
    //前向遍历
    while(p != NULL){
    
    
            //对结点p做相应处理
        p = p->prior;
    }
    //前向遍历(跳过头结点)
    while(p->prior != NULL){
    
    
            //对结点p做相应处理
        p = p->prior;
    }

循环链表

循环单链表

普通单链表表尾结点的next指针指向NULL,循环单链表表尾结点的next指针指向头结点

typedef struct LNode{
    
    
    int data;  //结点的数据域
    struct LNode *next;  //结点的指针域
}LNode,*LinkList;  //LinkList为指向结构体的指针类型

//初始化一个单链表(带头结点)
bool InitList(LinkList *L){
    
    
    L = (LNode *)malloc(sizeof(LNode));  //分配一个头结点
    if(L == NULL)
        return false;
    L->next = L;  //头结点的next指向头结点
    return true;
}
//判断单链表是否为空
bool Empty(LinkList *L){
    
    
    if(L->next == L)
        return true;
    else
        return false;
}

//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L, LNode *p){
    
    
    if(p->next == L)
        return true;
    else
        return false;
}

循环双链表

typedef struct DNode{
    
    
    int data;  //结点的数据域
    struct DNode *prior;  //指向结点的直接前驱
    struct DNode *next;  //指向结点的直接后继
}DNode,*DLinkList;  //LinkList为指向结构体的指针类型

bool InitDLinkList(DLinkList *L){
    
    
    L = (DNode *)malloc(sizeof(DNode));  //分配一个头结点
    if(L == NULL)  //内存不足分配失败
        return false;
    L->prior = L;  //头结点的prior永远指向头结点
    L->next = L;  //头结点的next指向头结点
    return true;
}

//判断循环双链表是否为空
bool Empty(DLinkList L){
    
    
    if(L->next == L)
        return true;
    else
        return false;
}

//在p结点之后插入s结点
bool InsertNextDNode(DNode *p,DNode *s){
    
    
    if(p == NULL || s == NULL)
        return false;
    s->next = p->next;
    if(p->next != NULL)
        p->next->prior = s;
    s->prior = p;
    p->next = s;
    return true;
}
//判断结点p是否为循环双链表的表尾结点
bool isTail(DLinkList L,DNode *p){
    
    
    if(p->next == L)
        return true;
    else
        return false;
}

静态链表

静态链表时用数组的方式实现链表
单链表各个结点在内存中散落分布
静态链表分配一整片连续的内存空间,各个结点集中放置
优点:增删操作不需要大量移动元素
缺点:不能随机存取,只能从头结点开始依次往后查找;容量固定不可变

#define MaxSize 10  //静态链表最大长度

struct Node{
    
      //静态链表结构类型定义
    int data;  //存储数据元素
    int next;  //下一个元素的数组下标
};
//可用SLinkList定义“一个长度为MaxSize的Node型数组”
typedef struct Node SLinkList[MaxSize];
void testLinkList(){
    
    
    struct Node a[MaxSize];  //数组a作为静态链表
}

等价于

#define MaxSize 10  //静态链表最大长度

typedef struct{
    
    
    int data;
    int next;
}SLinkList[MaxSize];


void testLinkList(){
    
    
    SLinkList a;
}

猜你喜欢

转载自blog.csdn.net/m0_46527751/article/details/122890282