关于数据结构之链表

本篇文章只适合刚开始学链表的同学哈,大佬不喜勿喷,希望大家可以在我从我这篇文章中有所收获哈!

线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),因此,为了表示某个数据元素a i aiai与它的直接后继元素a i + 1 ai+1ai+1之间的逻辑关系,对数据元素a i aiai来说,除存储其本身的信息之外,还需要存储一个指示它直接后继的信息(即直接后继的存储位置),这两部分信息组成数据元素a i aiai的存储映像,称为结点(node)。

它包括两个域:其中存储数据元素信息的域称为数据域,存储直接后继存储位置的域称为指针域,指针域中存储的信息称为指针或链,n nn个结点(a i aiai(1<=i<=n)的存储映像)链结成一个链表,即为线性表的链式存储结构

由于链表的每个结点中只包含一个指针域,所以又称这种链表为线性链表或单链表,整个链表的存取必须从头指针开始进行,头指针指示链表中的第一个结点(即第一个数据元素的存储映像)的存储位置,同时,由于最后一个数据元素没有直接后继,则线性链表中最后一个结点的指针为空(NULL)

在线性表的顺序存储结构中,由于逻辑上相邻的两个元素在物理位置上也相邻,则每个元素的存储位置都可以从线性表的起始位置计算得到,而在单链表中,任何两个元素的存储位置之间没有固定的联系,要想取得第i ii个数据元素必须从头指针出发寻找,因此,单链表是非随机存储结构。

单链表的局限性:无法逆向检索,有时候不方便

typedef struct LNode{           //定义单链表节点类型
    ElemType data;              //每个节点存放一个数据元素    
    struct LNode *next;         //指针指向下一个节点    
}LNode, *LinkList               

//初始化一个空的单链表
bool InitList(LinkList &L){
    L = NULL;       //空表,暂时没有任何结点(防止脏数据)
    return true;
}

void test(){
    LinkList L;     //声明一个指向单链表的指针
    // 初始化空表
    InitList(L);
    ...
}

//判断单链表是否为空
bool Empty(LinkList L){
    if(L == NULL){
        return true;
    }else{
        return false;
    }
}

typedef struct LNode{           //定义单链表节点类型
    ElemType data;              //每个节点存放一个数据元素    
    struct LNode *next;         //指针指向下一个节点    
}LNode, *LinkList               

//在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L, int i, ElemType e){
    if(i<1){
        return false;
    }
    LNode *p;       //指针p指向当前扫描的结点
    int j=0;        //当前p指针的是第几个结点
    while (p != NULL && j<i-1){     //循环找到第i-1个结点
        p = p->next;
        j++;//此时的p指针位于第i-1个结点中,指向的是第i个结点的位置
    }
    if(p == NULL){  //i值不合法
        return false;
    }
    LNode *t = (LNode *)malloc(sizeof(LNode));
    t->data = e;
    t->next = p->next;  //首先将新结点中的指针t指向原先位于第i个结点的地址
    p->next = t;        //将结点t连接到p之后,即t指针成为第i个结点的指针
    return true;        //插入成功
}

typedef struct LNode{           //定义单链表节点类型
    ElemType data;              //每个节点存放一个数据元素    
    struct LNode *next;         //指针指向下一个节点    
}LNode, *LinkList               

//带头结点的删除操作
bool ListDelete(LinkList &L, int i, ElemType &e){
    if(i<1){
        return false;
    }
    LNode *p;       //指针p指向当前扫描到的结点
    int j=0;        //当前p指向的是第几个结点
    p = L;          //L指向头结点,头结点是第0个结点(不存数据)
    while(p != NULL && j<i-1){      //循环找到第i-1个结点
        p = P->next;
        j++;
    }
    if(p == NULL){      //i值不合法
        return false;
    }
    if(p->next == NULL){        //第i-1个结点之后已无其他结点
        return false;
    }
    LNode *q = P->next;         //令q指向被删除结点
    e = q->data;                //用e返回元素的值
    p->next = q->next;          //将*q结点从链中断开
    free(q);                    //释放结点的存储空间
    return true;                //删除成功
}
以上文字摘自小田程序员,链接为数据结构与算法——线性表(链表篇)_线性链表-CSDN博客

了解了链表的大致结构,我们便开始接下来的具体的不同的链表的不同步骤的实现。

在展开具体描述前,我总结了一些我自己在学习中所遇到的难点,希望能对大家有帮助

疑难点:

1.指针类型为Node* ptr,意思就是指针指向的是下一个结点的位置,若有模板,则改为Node<T>* ptr,指针类型仍为Node,<T>的含义是指针所指向节点的数据域为T类型。

2.指针的调用:p->data就相当于(*p).data,p指针是指向结构体Node的指针;p->next就相当于(*p).next,即下一个节点的指针。

3.具体代码实现:链接

4.每个结点中的指针其实存储的都是下一个结点的地址,正因为如此,*p才能代表的是下一个结点,而(*p).data是下一个结点的数据,(*p).next是下一个结点的指针即p->next是下一个结点里的指针

5.有头结点的链表:有一个指针L称为头指针,它指向的是头结点,注意头指针和头结点的指针域是两个东西,头指针指向的地址是头结点的地址,而头结点中的地址域是next指针,指向的是首元结点(第一个结点)的地址。

6.重要操作:

p = L;//p指向头结点
s = L->next;//s指向首元结点
p = p->next;//p指向下一结点

7.if(p)是指指针p存在就是true,执行if语句的代码;if(!p)是指指针p若为空就是true,执行if语句的代码

!是非运算,在if判断语句中0为false,1为true。如果p为空(0),那么!p=true。

拓展:int a=5; if(!a) !a=0相当于false

8.第i个结点的next指针,存放的位置是在第i个结点的指针域,而该指针指向的地址是第i+1个结点的地址,因此若指针p为第i个结点里的next指针,则p->next是指第i+1个结点里的next指针所指向的地址,即第i+2个结点的地址。若指针p指向第i个结点的地址,则p->next(*p.next)是指向的第i+1个结点的地址。

注:大部分情况都是第二种,即指针p指向的是第i个结点的地址,记住p->next就是p指针所在结点的下一个结点的地址。

9.一般以头指针的名字当作链表的名字。


代码实现:

定义结构体
typedef struct Lnode{
    ElemType data;
    struct Lnode *next;
}LNode,*LinkList; //Linklist L表示Lnode类型的指针
构造空表
Status InitList_L(LinkList &L){
    L = new Lnode;//或 L=(LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    return OK;
}
//设置默认值或者返回一个表示初始化成功与否的状态码(Status),可能包括OK、ERROR等标识。

//判断头指针是否为空
int ListEmpty(LinkList L){
    if(L->next)//L->next=NULL相当于false
        return 0;
    else
        return 1;
}
销毁链表
//销毁链表是要将头结点也销毁,因此指针p先=头指针指向的头结点的地址
Status DestroyList_L(LinkList &L){
    Lnode *p;
    while(L){ //while(L)就相当于while(L!=NULL),即指针L指向的地址存在不等于空
        p = L;
        L = L->next;
        delete p;
    }
    return OK;
}
//由于销毁链表是将头结点也删除,因此头指针也要销毁,因此头指针也可以用来移动,因此这个函数只需要再创建一个指针p即可 
清空链表
//链表仍然存在,但链表中无元素,成为空链表,头指针和头结点还在 
//若指针p想指向头结点,则p=L;若指针p想指向首元结点,则p=L->next
//清空链表是将头结点仍然保留,因此头指针L需要保留,因此它不能移动,所以我们需要新创建两个指针,一个用来指向要删除的结点,一个用来指向下一个删除的结点
//若只用一个指针删出结点,则会导致找不到下一个删除的结点
Status ClearList(LinkList &L){
    Lnode *p,*q;
    p = L->next;
    while(p){
        q = p->next;
        delete p;
        p = q;
    }
    L->next = NULL;
    return OK;
}
链表表长
//从首元结点开始,依次计数
int ListLength_L(LinkList L){
    int i = 0;
    Lnode *p;
    p = L->next;//此时p指向的是首元结点的地址,若首元地址存在执行循环,若不存在直接返回i(0)
    while(p){
        i++;
        p = p->next;
    }
    return i;
}
取值:
Status GetElem_L(LinkList l,int i,ElemType &e){
    Lnode *p;
    p = L->next;//此时p指针已经指向首元结点了
    int j = 1;//计数器为1,表示第一个结点
    while(p&&j<i){
        p = p->next;
        ++j;
    }
    if(!p||j>i) 
        return ERROR;
    e = p->data;
    return OK;
}       
查找:
Lnode *LocateElem_L(LinkList L,Elemtype e){
    p = l->next;
    while(p&&p->data!=e)
        p = p->next;
    return p;//没找到就是返回空
}
int LocateElem_L(LinkList L,Elemtype e){
    p = l->next;
    int j = 1;
    while(p&&p->data!=e){
        p = p->next;
        j++;
    }
    if(p)
        return j;
    else
        return 0;
}

补:返回第i个结点的地址(更常用)

Lnode *LocatePosition_L(LinkList L,int i){
    Lnode *p;
    p = L;
    int j = 0;
    while(p&&j<i){
        p = p->next;
        j++;
    }
    if(!p||j>i-1)
        return NULL;
    return p;
}
插入:
Status ListInsert_L(LinkList &L,int i,ElemType e){
    Lnode *p;
    p = L;
    int j = 0;
    while(p&&j<i-1){ //我们首先要找的是第i-1个结点的位置
        p = p->next;
        j++;
    }//指针p指向第i-1个结点
    if(!p||j>i-1)//j>i-1是防止插入结点的位置小于1
        return ERROR;
    s = new LNode;//创建新结点
    s->data = e;
    s->next = p->next;//先将新结点与第i个结点相连
    p->next = s;//再将第i-1个结点与新结点相连
    return OK;
}
//这两部不可以交换,如果将新结点先与第i-1个结点相连,第i个结点就找不到了
//若非要先连第i-1个结点,需要再创建一个指针,指向第i个结点就可以了
删除:
Status ListDelete_L(LinkList &L,int i,ElemType &e){
    Lnode *p;
    p = L;
    int j = 0;
    while(p->next&&j<i-1){
        p = p->next;
        j++;
    }
    if(!(p->next)||j>i-1)
        return ERROR;
    Lnode *q;
    q = p->next;//q指针指向的是删除结点
    p->next = q->next;//或者写成p->next = p->next->next;
    e = q->data;
    delete q;
    return OK;
}

注:之所以这里while和if中的条件从p改成p->next是因为这里是删除操作,如果要删除最后一个结点,无需让指针指向空,让指针指向结点的next指针为空即可。

建立单链表
头插法:(倒位序)

void CreateList_H(LinkList &L,int n){
    L = new Lnode;
    L->next = NULL;//建立一个带头结点的单链表
    for(int i=n;i>0;i--){
        Lnode *p;
        p = new Lnode;
        cin>>p->data;
        p->next = L->next;//由于新结点要插在头结点之后,所以新结点的next指针指向的是原来头结点里的next指针所指向的
        L->next = p;//再将头结点的next指针指向新结点地址
    }
}
尾插法:(正位序)
void CreastList_R(LinkList &L,int n){
    L = new Lnode;
    L->next = NULL;
    Lnode *r;//尾结点
    r = L;
    for(int i=0;i<n;i++){
        Lnode*p;
        p = new Lnode;
        cin>>p->data;
        p->next = NULL;
        r->next = p;//原来r->next是空,现在要将r->next指向新的尾结点,目的就是让原来当尾结点的指针域指向新尾结点,进而连起来
        r = p;//尾指针指向新的尾结点
    }
}    

循环链表:

在尾插法建立单链表后,将尾指针r指向头结点,即r->next = L;

链表合并:
LinkList Connect(LinkList Ta,LinkList Tb){
    p = Ta->next;//存放链表Ta的头结点的地址
    Ta->next = Tb->next->next;//将链表Ta的尾结点与Tb的首元结点连起来
    delete Tb->next;
    Tb->next = p;
    return Tb;
}

双向链表:

定义结构体:
typedef struct DuLNode{
    Elemtype data;
    struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;

双向链表的插入:
Status ListInsert_DuL(DuLinkList &L,int i,ElemType e){
    //在第i个位置前插入元素e
    DuLNode *p;
    p = GetElemP_DuL(L,i);//让指针p指向第i个结点,查找方法与单链表一致
    //因为有前驱指针,因此直接指向第i个结点,而不是像之前单链表指向的是第i-1个结点
    //但是由于有两个指针,所以建立连接的时候要连两次
    if(!p) 
        return ERROR;
    s = new DuLNode;
    s->data = e;
    s->prior = p->prior;
    p->prior->next = s;
    s->next = p;
    p->prior = s;
    return OK;
}   

DuLNode *GetElemP_DuL(DuLinkList L,int i){
    DuLNode *p;
    p = L;
    int j = 0;
    while(p&&j<i){
        p = p->next;
        j++;
    }
    if(!p||j>i-1)
        return NULL;
    return p;
}
双向链表的删除

Status ListDelete_DuL(DuLink &L,int i,ElemType &e){
    DuLNode *p;
    p = GetElemP_DuL(L,i);
    if(!p)
        return ERROR;
    e = p->data;
    p->prior->next = p->next;//a的后继指针指向c
    p->next->prior = p->prior;//c的前驱指针指向a
    delete p;
    return OK;
}

DuLNode *GetElemP_DuL(DuLinkList L,int i){
    DuLNode *p;
    p = L;
    int j = 0;
    while(p&&j<i){
        p = p->next;
        j++;
    }
    if(!p||j>i-1)
        return NULL;
    return p;
}

时间复杂度比较:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值