数据结构之线性表(顺序表、单双向链表)

顺序表和链表 顺序表和链表的简单介绍和实现 阅读详情

一、顺序表

(一)顺序表的结构

可以将顺序表看作是一种结构体,该结构体中的成员有一个数组(用于存储数据)和两个变量(表示数组的实际长度与最大长度),但具体的成员设计有多种方式。

#define ElemType int
#define LIST_INIT_SIZE 100  //列表初始元素个数
#define LISTINCREMENT 10
typedef struct{  //一个顺序表结构体
    ElemType *elem;  //元素值(数组)
    int length;  //顺序表的实际长度
    int listsize;  //顺序表的最大长度
}SqList;

(二)初始化顺序表

初始化顺序表,首先要申请定长的内存(用malloc/calloc),大小=元素个数(自己定义的初始个数)*类型大小;申请后需要判空避免发生溢出申请失败;申请成功后更新实际元素个数和最大个数。

注意:传入参数必须是结构体的指针,才能申请空间修改其值可进行初始化。

//初始化顺序表
int InitList_Sq(SqList *L){
    //分配内存空间,列表初始元素个数*每个元素的类型大小
    L->elem=(ElemType *)calloc(LIST_INIT_SIZE,sizeof(ElemType));
    if(L->elem==NULL) return OVERFLOW; //分配失败,返回溢出错误
    L->length=0;  //初始化当前元素个数为0
    L->listsize=LIST_INIT_SIZE;  //初始化最大元素个数
    return OK;
}

(三)在顺序表中添加元素

int AddElem_Sq(SqList *L,ElemType *values,int count)

参数:需要添加元素的顺序表(传指针,修改值),需要添加的元素数组values和元素个数count。

思路:

  • 检查参数是否合法(传入的顺序表?数组?数量?);
  • 判断顺序表是否需要扩容,可以使用循环每次扩容的大小固定:使用临时变量进行扩容(不用顺序表直接扩容,防止扩容失败导致原来的数据丢失)并判空,扩容成功后再更新顺序表。
  • 利用循环逐个添加元素(从原原顺序表元素结尾开始),并更新实际元素个数length。
//添加元素
int AddElem_Sq(SqList *L,ElemType *values,int count){
    if(L==NULL || values==NULL || count<=0) return ERROR;  //检查参数
    while(L->length+count > L->listsize){//如果需要添加的元素个数>顺序表的最大长度,则需要对顺序表进行扩容
        //使用临时变量,防止realloc失败导致数据丢失
        ElemType *newElem=(ElemType *)realloc(L->elem,(L->listsize+LISTINCREMENT)*sizeof(ElemType));
        if(newElem==NULL) return OVERFLOW;
        L->elem=newElem;
        //更新最大长度
        L->listsize+=LISTINCREMENT;
    }
    //逐个添加元素
    for(int i=0;i<count;i++){
        L->elem[L->length+i]=values[i];
    }
    L->length+=count;
    return OK;
}

(四)插入元素

int ListInsert_Sq(SqList *L,int i,ElemType e)

参数:需要添加元素的顺序表(传指针,修改值),需要插入的位置i,待插入元素e。

思路:

  • 先检查参数是否合法;
  • 再判断是否需要扩容(与添加元素中的扩容方法一致);
  • 利用指针定位到需要插入的位置,从后向前使用循环依次向后覆盖,空出第i个位置赋值e,并更新顺序表的实际元素个数。
//插入元素
int ListInsert_Sq(SqList *L,int i,ElemType e){
    if(i<1 || i>L->length+1) return ERROR;
    //如果顺序表满了则需要扩容重新分配空间
    if(L->length >= L->listsize){
        //重新分配空间
        ElemType *newbase=(ElemType *)realloc(L->elem,(L->listsize+LISTINCREMENT)*sizeof(ElemType));
        if(!newbase) exit(OVERFLOW);
        //分配成功则更新顺序表的基地址与最大长度
        L->elem=newbase;
        L->listsize+=LISTINCREMENT;
    }
    //定位需要插入的位置
    ElemType *q=L->elem+i-1;
    //从后往前以此向后覆盖空出第i位
    for(ElemType *p=L->elem+L->length-1;p >= q;--p){
        *(p+1)=*p;
    }
    *q=e;  //插入第i位
    ++L->length; //更新顺序表长度
    return OK;
}

(五)删除元素

int ListDelete_Sq(SqList *L,int i,ElemType *e)

参数:需要添加元素的顺序表(传指针,修改值),需要删除元素的位置i,e存放删除元素。

思路:先检查参数;然后定位到需要删除的元素的位置,用参数e暂存删除的元素值,使用循环将待删除元素的后一个元素向前覆盖待删元素,直到顺序表的最后一个元素,最后更新实际元素个数。

//删除元素
int ListDelete_Sq(SqList *L,int i,ElemType *e){
    if(i<1 || i>L->length) return ERROR;
    ElemType *p=L->elem+i-1;  //定位需要删除元素的位置
    *e=*p;  // 保留需要删除的元素值
    ElemType *q=L->elem+L->length-1;
    for(++p;p <= q;++p){
        *(p-1)=*p;
    }
    --L->length;  //更新长度
    return OK;
}

(六)有序顺序表的合并

void MergeList_Sq(SqList *La,SqList *Lb,SqList *Lc)

参数:两个有序顺序表La,Lb,合并的结果放在顺序表Lc。

思路:

  • 定义两个指针pa和pb,分别从两个表的开头开始遍历;再定义两个指针pa_last,pb_last,用于表示两个顺序表的边界,判断是否遍历完。
  • 动态分配足够的内存空间给合并顺序表Lc,总容量为两个表容量之和,实际元素个数为两个表长度之和。
  • 循环遍历两个有序顺序表,每次比较pa和pb对应的元素,如果pa的元素更小,将它放入lc,然后后移pa与pc,反之则将pb的元素放入Lc并后移pb与pc。
  • 当其中一个表遍历完,另一个表可能还有剩余元素,直接将剩余元素依次放入Lc(因为两个表本身有序,剩余元素一定都大于已放入的元素)。
//顺序表的合并
void MergeList_Sq(SqList *La,SqList *Lb,SqList *Lc){
    ElemType *pa,*pb,*pc;
    ElemType *pa_last,*pb_last;
    pa=La->elem;
    pb=Lb->elem;
    Lc->listsize=La->listsize+Lb->listsize;  //更新合并顺序表的最大元素个数
    Lc->length=La->length+Lb->length;  //更新合并顺序表的实际元素个数
    Lc->elem=(ElemType *)malloc(Lc->listsize*sizeof(ElemType));  //申请空间
    if(!Lc->elem) return OVERFLOW;
    pc=Lc->elem;
    pa_last=La->elem+La->length-1;  //设定La边界
    pb_last=Lb->elem+Lb->length-1;  //设定Lb边界
    while(pa<=pa_last && pb<=pb_last){  //循环遍历两个有序顺序表并比较对应元素值
        if(*pa<*pb){  //将较小元素值更新到顺序表Lc
            *pc++=*pa++;
        }else{
            *pc++=*pb++;
        }
    }
    while(pa<=pa_last) *pc++=*pa++;  //将La剩余元素更新到合并顺序表Lc
    while(pb<=pb_last) *pc++=*pb++;  //将Lb剩余元素更新到合并顺序表Lc
}

顺序表测试全部代码:

#include <stdio.h>
#include <stdlib.h>
#define OVERFLOW 0
#define ERROR 0
#define OK 1

#define ElemType int
#define LIST_INIT_SIZE 100  //列表初始元素个数
#define LISTINCREMENT 10
typedef struct{  //一个顺序表结构体
    ElemType *elem;  //元素值(数组)
    int length;  //顺序表的实际长度
    int listsize;  //顺序表的最大长度
}SqList;

//初始化顺序表
int InitList_Sq(SqList *L){
    //分配内存空间,列表初始元素个数*每个元素的类型大小
    L->elem=(ElemType *)calloc(LIST_INIT_SIZE,sizeof(ElemType));
    if(L->elem==NULL) return OVERFLOW; //分配失败,返回溢出错误
    L->length=0;  //初始化当前元素个数为0
    L->listsize=LIST_INIT_SIZE;  //初始化最大元素个数
    return OK;
}

//添加元素
int AddElem_Sq(SqList *L,ElemType *values,int count){
    if(L==NULL || values==NULL || count<=0) return ERROR;  //检查参数
    while(L->length+count > L->listsize){//如果需要添加的元素个数>顺序表的最大长度,则需要对顺序表进行扩容
        //使用临时变量,防止realloc失败导致数据丢失
        ElemType *newElem=(ElemType *)realloc(L->elem,(L->listsize+LISTINCREMENT)*sizeof(ElemType));
        if(newElem==NULL) return OVERFLOW;
        L->elem=newElem;
        //更新最大长度
        L->listsize+=LISTINCREMENT;
    }
    //逐个添加元素
    for(int i=0;i<count;i++){
        L->elem[L->length+i]=values[i];
    }
    L->length+=count;
    return OK;
}

//插入元素
int ListInsert_Sq(SqList *L,int i,ElemType e){
    if(i<1 || i>L->length+1) return ERROR;
    //如果顺序表满了则需要扩容重新分配空间
    if(L->length >= L->listsize){
        //重新分配空间
        ElemType *newbase=(ElemType *)realloc(L->elem,(L->listsize+LISTINCREMENT)*sizeof(ElemType));
        if(!newbase) exit(OVERFLOW);
        //分配成功则更新顺序表的基地址与最大长度
        L->elem=newbase;
        L->listsize+=LISTINCREMENT;
    }
    //定位需要插入的位置
    ElemType *q=L->elem+i-1;
    //从后往前以此向后覆盖空出第i位
    for(ElemType *p=L->elem+L->length-1;p >= q;--p){
        *(p+1)=*p;
    }
    *q=e;  //插入第i位
    ++L->length; //更新顺序表长度
    return OK;
}

//删除元素
int ListDelete_Sq(SqList *L,int i,ElemType *e){
    if(i<1 || i>L->length) return ERROR;
    ElemType *p=L->elem+i-1;  //定位需要删除元素的位置
    *e=*p;  // 保留需要删除的元素值
    ElemType *q=L->elem+L->length-1;
    for(++p;p <= q;++p){
        *(p-1)=*p;
    }
    --L->length;  //更新长度
    return OK;
}

//顺序表的合并
void MergeList_Sq(SqList *La,SqList *Lb,SqList *Lc){
    ElemType *pa,*pb,*pc;
    ElemType *pa_last,*pb_last;
    pa=La->elem;
    pb=Lb->elem;
    Lc->listsize=La->listsize+Lb->listsize;  //更新合并顺序表的最大元素个数
    Lc->length=La->length+Lb->length;  //更新合并顺序表的实际元素个数
    Lc->elem=(ElemType *)malloc(Lc->listsize*sizeof(ElemType));  //申请空间
    if(!Lc->elem) exit(OVERFLOW);
    pc=Lc->elem;
    pa_last=La->elem+La->length-1;  //设定La边界
    pb_last=Lb->elem+Lb->length-1;  //设定Lb边界
    while(pa<=pa_last && pb<=pb_last){  //循环遍历两个有序顺序表并比较对应元素值
        if(*pa<*pb){  //将较小元素值更新到顺序表Lc
            *pc++=*pa++;
        }else{
            *pc++=*pb++;
        }
    }
    while(pa<=pa_last) *pc++=*pa++;  //将La剩余元素更新到合并顺序表Lc
    while(pb<=pb_last) *pc++=*pb++;  //将Lb剩余元素更新到合并顺序表Lc
}


int main(){
    SqList List;
    InitList_Sq(&List);
    ElemType values[]={10,20,30,40,50};
    int count=sizeof(values)/sizeof(values[0]);
    AddElem_Sq(&List,values,count);
    for(int i=0;i<List.length;i++){
        printf("%d ",List.elem[i]);
    }
    printf("\n");
    ElemType x=25;
    ListInsert_Sq(&List,3,x);
    for(int i=0;i<List.length;i++){
        printf("%d ",List.elem[i]);
    }
    printf("\n");
    ElemType e=0;
    ListDelete_Sq(&List,3,&e);
    for(int i=0;i<List.length;i++){
        printf("%d ",List.elem[i]);
    }

    SqList List1,List2,List3;
    InitList_Sq(&List1);
    InitList_Sq(&List2);
    ElemType values1[]={11,21,31,41,51};
    ElemType values2[]={12,22,32,42,52};
    int count1=sizeof(values1)/sizeof(values1[0]);
    int count2=sizeof(values2)/sizeof(values2[0]);
    AddElem_Sq(&List1,values1,count1);
    AddElem_Sq(&List2,values2,count2);
    MergeList_Sq(&List1,&List2,&List3);
    printf("\n");
    for(int i=0;i<List3.length;i++){
        printf("%d ",List3.elem[i]);
    }
    return 0;
}

二、单链表

(一)单链表结构

单链表有很多个结构体(结点)组成,每个结构体有数据域(变量,存放数据)和指针域(指针,存放下一个结构体的地址)。

#define ElemType int
typedef struct LNode{
    ElemType data;
    struct LNode *next;
//LNode为结点,LinkList为指向结点的指针
}LNode,*LinkList;

(二)创建链表

先创建头结点(只作为链表的起始,不存储数据)并初始化为NULL,需要使用二级指针(因为要在函数内修改外部的指针变量)。

  • 尾插法:定义尾指针tail并初始化为头结点,表示当前链表的尾部,用于从前往后添加结点。使用循环,为新元素申请分配空间,并提示从键盘输入新结点元素值。将链表的尾结点的next指向新建结点tail->next = p,并更新尾指针tail = p。
  • 前插法:不需要额外定义指针,直接使用头结点(每次在头结点插入新节点)。使用循环,为新元素申请分配空间,并提示从键盘输入新结点元素值。将新结点的next指向第一个元素(头结点的后继)p->next=(*L)->next,再将头结点的next指向新结点(*L)->next=p。(这两步顺序不能改变,如果改变会导致数据丢失)

注意:使用头插法,输入元素的顺序与实际链表中元素的顺序相反。尾插法顺序一致。

//创建链表:头插法+尾插法
int CreateList_L(LinkList *L,int n){
    //创建一个链表(指针),L指向头结点
    //L为二级指针:指向LinkList,LinkList指向LNode
    //所以*L为指向LNode
    *L=(LinkList)malloc(sizeof(LNode));
    if(*L==NULL){  //*L为指针指向头结点
        printf("Memory allocation failed!\n");
        return ERROR;
    }
    //初始化链表为空链表
    (*L)->next=NULL;
    LNode *tail=*L;  //尾指针
    //创建链表,插入n个元素
    for(int i=0;i<n;i++){
        //创建新结点,p为指针指向新结点
        LNode *p=(LNode *)malloc(sizeof(LNode));
        if(p==NULL){
            printf("Memory allocation failed!\n");
            return ERROR;
        }
        //printf("请输入第%d个元素:",n-i);  //头插法:逆序插入
        printf("请输入第%d个元素:",i+1);  //尾插法:顺序插入
        scanf("%d",&p->data);
        p->next=NULL;
        tail->next=p;  //尾插法:使用尾指针从前往后链接
        tail=p;
        // p->next=(*L)->next;  //头插法:先链接后面再链接前面
        // (*L)->next=p;
    }
    return OK;
}

(三)插入结点

定义一个指针p使用循环用于定位需要插入位置的前驱(如在第一个位置插入则p指向头结点,在头结点后面插入结点),定位后需要检查是否遍历到链表尾部或位置的合法性。创建新结点并赋值,先链接后面(将新结点的next指向原链表中第i个结点s->next = p->next),再链接后面(更新第i-1个结点的next指向新结点p->next = s)。

//在第i个位置插入结点
int ListInsert_L(LinkList *L,int i,ElemType e){
    LinkList p=*L;
    int j=0;
    while(p && j<i-1){  //定位到需要插入的地方
        p=p->next;
        ++j;
    }
    //遍历到链表尾或i值非法(为0/负值)
    if(!p || j>i-1) return ERROR;
    //创建新结点
    LinkList s=(LinkList)malloc(sizeof(LNode));
    if(!s) return ERROR;
    s->data=e;
    s->next=p->next; //先链接后面再链接前面
    p->next=s;
    return OK;
}

(四)查找结点

定义一个指针p使用循环用于定位需要查找结点的位置(如需要查找第i个位置的结点元素),将其数据域的元素存入e。

//查找第i个结点,并存入e
int GetElem_L(LinkList L,int i,ElemType *e){
    LinkList p=L->next;
    int j=0;
    while(p && j<i-1){
        p=p->next;
        ++j;
    }
    if(!p || j>i) return ERROR;
    *e=p->data;
    return OK;
}

(五)删除结点

定义一个指针p使用循环用于定位需要删除结点的前驱(如需要删除第i个结点则定位到第i-1个结点),然后用指针q指向待删结点q=p->next,将其数据域存入e中q=p->next,最后修改指针跳过直接指向q的后继结点 p->next=q->next,并释放删除结点的内存 free(q)。

//删除第i个结点并将其存入e
int ListDelete_L(LinkList *L,int i,ElemType *e){
    LinkList q,p=*L;//p为待删元素的前驱
    int j=0;
    while(p && j<i-1){
        p=p->next;
        ++j;
    }
    if(!(p->next) || j>i-1) return ERROR;
    q=p->next;
    p->next=q->next;
    *e=q->data;
    free(q);
    return OK;
}

(六)合并有序链表(原地操作)

La与Lb为待合并的有序链表,Lc指向La(将Lb合并(插入)到链表La上)。利用循环使pa与pb分别遍历La与Lb比较对应的结点数据域,每次将较小的结点连接到pc后面pc->next=pa;并更新其指向 pc=pa、pa=pa->next。循环结束后将剩余链表链接到pc后面,最后释放Lb的头结点并置空。

//合并有序链表
void MergeList_L(LinkList *La,LinkList *Lb,LinkList *Lc){
    LinkList pa=(*La)->next;
    LinkList pb=(*Lb)->next;
    LinkList pc;
    *Lc=*La;  //在La上合并
    pc=*La;  //以指针pc进行遍历合并
    while(pa && pb){
        if(pa->data <= pb->data){
            pc->next=pa;  //先链接后移动
            pc=pa;
            pa=pa->next;
        }else{
            pc->next=pb;
            pc=pb;
            pb=pb->next;
        }
    }
    pc->next=pa?pa:pb;  //链接剩余链表
    free(*Lb);//释放Lb
    *Lb=NULL;//防止野指针
}

单链表测试完整代码:

#include <stdio.h>
#include <stdlib.h>
#define ElemType int
#define ERROR 0
#define OK 1
//结点
typedef struct LNode{
    ElemType data;
    struct LNode *next;
//LNode为结点,LinkList为指向结点的指针
}LNode,*LinkList;

//创建链表:头插法+尾插法
int CreateList_L(LinkList *L,int n){
    //创建一个链表(指针),L指向头结点
    //L为二级指针:指向LinkList,LinkList指向LNode
    //所以*L为指向LNode
    *L=(LinkList)malloc(sizeof(LNode));
    if(*L==NULL){  //*L为指针指向头结点
        printf("Memory allocation failed!\n");
        return ERROR;
    }
    //初始化链表为空链表
    (*L)->next=NULL;
    LNode *tail=*L;  //尾指针
    //创建链表,插入n个元素
    for(int i=0;i<n;i++){
        //创建新结点,p为指针指向新结点
        LNode *p=(LNode *)malloc(sizeof(LNode));
        if(p==NULL){
            printf("Memory allocation failed!\n");
            return ERROR;
        }
        //printf("请输入第%d个元素:",n-i);  //头插法:逆序插入
        printf("请输入第%d个元素:",i+1);  //尾插法:顺序插入
        scanf("%d",&p->data);
        p->next=NULL;

        tail->next=p;  //尾插法:使用尾指针从前往后链接
        tail=p;
        // p->next=(*L)->next;  //头插法:先链接后面再链接前面
        // (*L)->next=p;
    }
    return OK;
}

//在第i个位置插入结点
int ListInsert_L(LinkList *L,int i,ElemType e){
    LinkList p=*L;
    int j=0;
    while(p && j<i-1){  //定位到需要插入的地方
        p=p->next;
        ++j;
    }
    //遍历到链表尾或i值非法(为0/负值)
    if(!p || j>i-1) return ERROR;
    //创建新结点
    LinkList s=(LinkList)malloc(sizeof(LNode));
    if(!s) return ERROR;
    s->data=e;
    s->next=p->next; //先链接后面再链接前面
    p->next=s;
    return OK;
}

//查找第i个结点,并存入e
int GetElem_L(LinkList L,int i,ElemType *e){
    LinkList p=L->next;
    int j=0;
    while(p && j<i-1){
        p=p->next;
        ++j;
    }
    if(!p || j>i) return ERROR;
    *e=p->data;
    return OK;
}

//删除第i个结点并将其存入e
int ListDelete_L(LinkList *L,int i,ElemType *e){
    LinkList q,p=*L;//p为待删元素的前驱
    int j=0;
    while(p && j<i-1){
        p=p->next;
        ++j;
    }
    if(!(p->next) || j>i-1) return ERROR;
    q=p->next;
    p->next=q->next;
    *e=q->data;
    free(q);
    return OK;
}

//合并有序链表
void MergeList_L(LinkList *La,LinkList *Lb,LinkList *Lc){
    LinkList pa=(*La)->next;
    LinkList pb=(*Lb)->next;
    LinkList pc;
    *Lc=*La;  //在La上合并
    pc=*La;  //以指针pc进行遍历合并
    while(pa && pb){
        if(pa->data <= pb->data){
            pc->next=pa;  //先链接后移动
            pc=pa;
            pa=pa->next;
        }else{
            pc->next=pb;
            pc=pb;
            pb=pb->next;
        }
    }
    pc->next=pa?pa:pb;  //链接剩余链表
    free(*Lb);//释放Lb
}

int main(){
    LinkList L;
    int n;
    printf("请输入链表的长度:");
    scanf("%d",&n);
    CreateList_L(&L,n);
    //头结点没有数据,next指向第一个数据
    LNode *p=L->next;
    while(p!=NULL){
        printf("%d ",p->data);
        p=p->next;
    }
    int i,e;
    printf("\n请输入插入位置和元素:");
    scanf("%d%d",&i,&e);
    ListInsert_L(&L,i,e);
    p=L->next;
    while(p!=NULL){
        printf("%d ",p->data);
        p=p->next;
    }
    GetElem_L(L,3,&e);
    printf("\n第3个元素为%d\n",e);
    ListDelete_L(&L,3,&e);
    printf("删除第3个元素:");
    p=L->next;
    while(p!=NULL){
        printf("%d ",p->data);
        p=p->next;
    }

    LinkList L1,L2,L3;
    printf("\n请输入两个有序链表的长度:");
    scanf("%d",&n);
    printf("\n请输入第一个有序链表:\n");
    CreateList_L(&L1,n);
    printf("\n请输入第二个有序链表:\n");
    CreateList_L(&L2,n);
    MergeList_L(&L1,&L2,&L3);
    //头结点没有数据,next指向第一个数据
    LNode *q=L3->next;
    while(q){
        printf("%d ",q->data);
        q=q->next;
    }
    return 0;
}

(七)线性链表的逆置(反转)

先检查是否为空链表或只有一个元素,这两种情况都不需要进行反转。定义三个指针prev(p),curr(c),next(n)遍历链表,逐个改变结点的next指针方向,实现原地反转。最后将头结点指向反转后的新头结点。

注意:可以将此过程看作顺序遍历整个链表,逐个将结点按照头插法依次插入原链表(头插法:输入元素的顺序与实际链表中元素的顺序相反)。

//线性链表的逆置
int reverseListWithHeader(LinkList L){
    //空链表或只有一个元素时不需要反转
    if(L==NULL || L->next==NULL) return OK;
    LNode *prev=NULL;
    LNode *curr=L->next;
    LNode *next=NULL;
    while(curr!=NULL){
        next=curr->next;  //下一个需要插入的结点
        curr->next=prev;  //利用头插法插入结点
        prev=curr;  //更新第一个结点
        curr=next;  //更新当前插入结点
    }
    L->next=prev;  //头结点指向反转后的新头结点
    return OK;
}

三、单链表扩展(无头结点)

(一)删除链表中所有等于val的结点

定义一个哑结点dummy作为首结点(第一个数据结点)的前驱,哑结点的next指向原链表头,统一处理首结点删除的情况。

定义一个指针current从哑结点开始遍历链表,如果current下一个结点的值等于val,则删除下一个结点(将current的next直接链接到current后继的后继),最后释放哑结点并返回新首结点。

//删除链表中所有满足 Node.val==val 的结点,并返回新的头结点
LinkList deleteNode_L(LinkList head,int val){
    if(head==NULL) return NULL; //空链表
    //没有头结点,所以需要定义一个哑结点作为首结点的前驱
    //避免首结点就等于val无法删除,使用哑结点方便操作
    LinkList dummy=(LinkList)malloc(sizeof(LNode));
    if(dummy==NULL) return NULL; 
    dummy->next=head;  //作用与头结点一致(无数据,指向首结点)
    //从哑结点开始往后遍历链表,依次删除==val的结点
    LinkList current=dummy;   
    while(current->next!=NULL){
        if(current->next->val==val){//比较下一个结点的数据是否等于val
            LinkList temp=current->next;  //暂存删除结点
            current->next=current->next->next;  //当前结点直接链接删除结点的后继(跳过链接)
            free(temp);  //将删除的结点释放
        }else{
            current=current->next;  //遍历链表
        }
    }
    LinkList newNode=dummy->next;  //用于返回首结点
    free(dummy);  //释放内存,防止内存泄漏
    //释放后不需要再置空了,因为dummy为局部变量,函数结束后会自动销毁
    return newNode;
}

(二)返回链表的中间节点

使用快慢指针,利用循环遍历整个链表,快指针fast每次都两步,慢指针slow每次只走一步。当快指针fast到达链表尾部时,慢指针刚好指向链表的中间结点。

注意:初始化时slow和fast都指向首结点(第一个有数据的结点,非头结点),当链表长度为偶数时,慢指针slow会指向第二个中间结点。

//返回链表的中间节点,如果有两个中间节点,则返回第二个中间节点
LinkList searchMiddleNode_L(LinkList head){
    if(head==NULL) return NULL;
    LinkList slow=head;
    LinkList fast=head;
    while(fast!=NULL && fast->next!=NULL){
        slow=slow->next;  //慢指针slow每次只走一步
        fast=fast->next->next;  //快指针fast每次都两步
    }
    return slow;
}

(三)返回链表中的倒数第k个结点

定义两个指针p和q并初始化为首结点(第一个有数据的结点,非头结点),先让指针p走k步,此时p与q相差k-1(中间有k-1个节点),然后p和q同时移动,当p到达链表末尾时(p==NULL),q刚好指向倒数第k个结点(p与q相差k-1个结点,p此时为NULL,则q为倒数第k个结点)。

//返回链表中的倒数第k个节点
LinkList searchNode_L(LinkList head,int k){
    if(k<=0 || head==NULL) return NULL;
    LinkList p=head;
    LinkList q=head;
    for(int i=0;i<k;i++){
        if(p==NULL) return NULL;  //不存在k个结点
        p=p->next;  //定位到第k个结点
    }//此时p与q相差(中间有)k-1个结点
    while(p!=NULL){  //定位后p,q同时移动
        p=p->next;
        q=q->next;
    }
    return q;
}

四、双向链表

(一)双向链表的结构

双向链表与单链表的不同是,多了一个前向指针prior。

#define ElemType int
typedef struct DuLNode{
    ElemType data;
    struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;

(二)创建双向空链表

申请分配一个头结点的内存,将其prior和next指针都指向自身,形成一个双向循环空链表。

注意:空链表中,头结点的prior和next都指向自己,这是双向循环链表的标志。

//创建一个带头结点的双向空链表
DuLinkList CreateList_DuL(){
    DuLinkList L=(DuLinkList)malloc(sizeof(DuLNode));
    if(L==NULL) return NULL;
    L->prior=L;
    L->next=L;
    return L;
}

(三)按位置查找结点

从头结点的后继(首元素)开始遍历,计数到第 i 个结点(数据结点),返回该结点的指针。特殊处理i=0时返回头结点(无数据,首元素的前驱)。

//按位置查找结点
DuLinkList GetElem_DuL(DuLinkList L,int i){
    if(L==NULL || i<0) return NULL;
    if(i==0) return L;
    DuLinkList p=L->next;
    int j=1;
    while(p!=L && j<i){  //循环遍历找到位置
        p=p->next;
        ++j;
    }
    //p==L说明已经遍历完整个链表,没有k个结点
    if(p==L || j!=i) return NULL;
    return p;
}

(四)插入结点

调用查找函数定位到第 i-1 个结点(插入位置的前驱),创建新结点p并申请空间,申请成功设置数据值,链接新结点(需要设置链表第i-1个结点的next、第i个结点的prior 与 新结点的prior、next进行链接)。

注意:连接顺序很重要,必须先链接新结点,再修改链表第i-1个结点的next、第i个结点的prior。

//插入第i个元素:尾插法(第一个位置和最后一个位置都能实现插入操作)
int ListInsert_DuL(DuLinkList *L,int i,ElemType e){
    DuLinkList p=GetElem_DuL(*L,i-1); //定位到第i个元素的前驱
    if(p == NULL) return ERROR;
    DuLinkList s=(DuLinkList)malloc(sizeof(DuLNode));//创建新结点
    if(s==NULL) return ERROR;
    s->data=e;  //赋值
    s->prior=p;  //新结点的prior指针指向第i个结点的前驱p
    s->next=p->next;  //新结点的next指向p的后继(原链表第i个结点)
    p->next->prior=s;  //将原链表第i个结点连接到新结点的后面
    p->next=s;  //将新结点连接到p的后面
    return OK;
}

(五)删除结点

调用查找函数定位到第 i 个结点(待删结点),先暂存待删结点的数据,在修改其前后指针(将原链表第i-1个结点的next与第i+1个结点的prior链接),将待删结点从链表中摘除,最后释放删除结点的内存。

//删除第i个结点
int ListDelete_DuL(DuLinkList *L,int i,ElemType *e){
    DuLinkList p=GetElem_DuL(*L,i);
    if(p==NULL) return ERROR;
    *e=p->data;  //暂存数据
    p->prior->next=p->next;  //修改前后指针
    p->next->prior=p->prior;
    free(p);
    return OK;
}

数据结构之线性表(顺序表和链表) 线性表 首先线性表的定义就是:零个或多个数据元素的有限序列。 列如高考前每个班级排队照准考证,这个队伍就可以看做一个线性表,大家都井然有序的排着队,是一个有限的序列,一个班就那么几十个人,而且每个人之间都是有顺序的,站错了顺序就不好核对信息。相对于在广场上一群人在跳广场舞、打拳、散步,这就不是线性表,首先它不是有序的序列,而是散乱的一堆,而且相对来说不是有限的。计算机处理的对象都是有限的,... 阅读详情

相关推荐

modbus

从机地址 功能码 起始地址高位 起始地址低位 数量高位 数量低位 字节数 0001H高位 0001H低位 0002H高位 0002H低位 CRC高位 CRC低位。每个输入寄存器的长度为2个字节。其中,CDH对应线圈0013H到001AH的内容,01H对应线圈001B到001CH的内容,未使用位用0填充。从机地址 功能码 起始地址高位 起始地址低位 寄存器数量高位 寄存器数量低位 CRC高位 CRC低位。从机地址 功能码 起始地址高位 起始地址低位 寄存器数量高位 寄存器数量低位 CRC高位 CRC低位。

lunhuicnm的博客 9715

数据结构:顺序表和链表

静态顺序表主要适用于明确知道需要存放多少数据的场景,但是在实际不一定知道数据的个数,定长数组开大了浪费空间,开小了不够存放,所以一般使用动态顺序表。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空结点。新链表是通过拼接给定的两个链表的所有结点组成的。编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前。、非顺序的存储结构,数据元素的。

decade777555的博客 178

Gromacs基础教程一:入门建议

本系列教程针对的群体:非计算化学专业,而是偏生物方向的、有少量分子动力学模拟需求的人群。

m0_67843839的博客 8561

《数据结构初阶》【顺序表 + 单链表 + 双向链表】

博客内容主要划分为:数据结构的介绍 + 数据结构的实现。主要涉及的数据结构:顺序表、单链表、双向链表。

序属秋秋秋的博客 2531

Ubuntu 22.04蓝牙开关秒关?别慌,可能是这个Intel固件文件在搞鬼

本文深入解析Ubuntu 22.04蓝牙开关秒关问题,指出Intel固件文件缺失是常见原因,并提供详细修复方案。通过确认硬件型号、检查固件文件及智能替代方案,帮助用户快速解决蓝牙故障,提升系统稳定性。

weixin_30247307的博客 753

从零构建机械臂规划栈:Pinocchio、OMPL与Ruckig的工程化融合实践

本文详细介绍了如何从零构建机械臂规划栈,通过融合Pinocchio、OMPL和Ruckig三大核心组件,实现高性能的机械臂运动规划与轨迹生成。文章深入探讨了技术选型、工程架构设计及实战优化技巧,特别针对逆解、路径规划和轨迹平滑等关键环节提供了实用解决方案,适用于工业场景中的高实时性需求。

weixin_29072299的博客 430

【数据结构篇C++实现】- 线性表 - 顺序表和链表

线性表知识总结,一文搞懂什么是顺序表与链表,以及常见的操作

陈七的博客,学习知识分享,期待与大家的交流 4256

Isaacgym使用操作指南

Isaac Gym 是一个强大的仿真工具,特别适合那些需要进行大规模并行仿真和训练的机器人和强化学习任务。通过 GPU 加速、深度学习集成和丰富的物理仿真能力,Isaac Gym 能够显著提高仿真和训练效率,是机器人学和 AI 研究中的一大利器。

Tony Wey的博客 1万+

线性表——顺序表,单向链表和双向链表

博客主页(4条消息) 小吴_小吴有想法_CSDN博客-笔记,java,leetcode领域博主 欢迎关注点赞收藏和留言 天道酬勤,勤能补拙,和小吴一起加油吧 17岁大一新生,水平有限,恳请各位大佬指点,不胜感激! 参考书籍《算法4》,学习视频:黑马程序员Java数据结构与java算法,全网资料最全数据结构+算法教程,154张java数据结构图_哔哩哔哩_bilibili 目录 线性表 顺序表 代码实现 时间复杂度 链表 单向链表 单向链表代码实现 双向链表 代码...

小吴 1506

Xilinx XDMA IP核配置实战:AXI Memory Mapped vs Stream选型指南(附PCIe调试技巧)

本文深入解析Xilinx XDMA IP核配置中AXI Memory Mapped与AXI Stream两种DMA接口的核心区别与选型策略。针对PCIe高速数据传输场景,详细对比了二者在数据特性、延迟、硬件资源及软件复杂度上的差异,为FPGA工程师在数据采集、实时处理等项目中做出高效设计决策提供实战指南,并附有关键的PCIe调试技巧。

qsc901234的博客 580

AUTOSAR从入门到精通-【应用实战篇】车载HMI-ADAS

ADAS是高级驾驶辅助系统(Advanced Driving Assistance System)简称ADAS。ADAS高级驾驶辅助系统是利用安装于车上的各式各样的传感器, 在第一时间收集车内外的环境数据, 进行静、动态物体的辨识、侦测与追踪等技术上的处理, 从而能够让驾驶者在最快的时间察觉可能发生的危险, 以引起注意和提高安全性的主动安全技术。

lin_xiu的博客 1428

【数据结构】顺序表和链表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。

2301_80302655的博客 1821

uniapp开发小程序保存图片权限获取失败一直走fail?本地能保存,线上版就不行! 原来是这个问题!

通过这次经历,我发现线上版小程序和开发版、体验版的权限控制差异较大。如果你也遇到类似的问题,首先要检查是否已勾选“采集用户隐私”,并确保在提交审核时配置了隐私条款。这个设置非常关键,避免像我一样浪费了不少时间和版本。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1073

数据结构 顺序表与链表

摘要 本文介绍了Java集合框架中的线性表结构,重点分析了ArrayList和LinkedList的实现原理与使用方式。主要内容包括:1) 线性表的基本概念及List接口特性;2) ArrayList的顺序表实现,涵盖构造方法、常用操作、遍历方式和1.5倍自动扩容机制;3) LinkedList的链表实现,说明链表数据结构特点及分类。文章详细对比了两种数据结构的优缺点,指出ArrayList的连续存储特性导致插入删除效率低O(N)和空间浪费问题,而链表结构可以解决部分问题。最后提供了ArrayList核心方

双吉堡工程专业的一名27届Javaer。 1776

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

【数据结构与算法】之线性表:顺序表和链表个人总结

定义:线性表是具有相同特性的数据元素的一个有限序列。特点:各个数据元素之间是线性关系。在非空的线性表中,有且仅有一个开始结点,它没有直接前趋,而仅有一个直接后继。有且仅有一个终端结点,它没有直接后继,而仅有一个直接前趋。其余内部结点都有且仅有一个直接前趋和一个直接后继。

2201_75455144的博客 2190

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

数据结构 顺序表和链表

顺序表和链表的实现需要一定的c语言代码能力,需要掌握动态内存管理的相关知识,会使用mallocreallocfree等函数1.中间头部插入,删除数据,需要挪动数据,效率低下。2,空间不够,扩容,扩容有一定损耗,其次可能有一定的空间浪费。

weixin_67645591的博客 1832

顺序表和链表【数据结构】【基于C语言实现】【一站式速通】

本文详细介绍了单向链表,双向链表的实现,深入分析了各种数据结构的优缺点,剖析了它们的演变过程,通俗易懂,万字总结,小白无痛学习,同时也欢迎大牛的指导~

2301_79241364的博客 1811

【数据结构初阶-线性表】顺序表和链表,多图详解

细细咀嚼...

BaconZzz的博客 1928

线性表——顺序表和链表

顺序表和链表的解释与实现

qq_65285898的博客 3818

【数据结构】线性表完全指南:顺序表与链表详解

线性表指的是具有相同数据类型的n(n>=0)个数的有限序列。假设a1是第一个数据元素,称为表头元素;an是最后一个数据元素,称为表尾元素;ai(1<i<n)是第i个数据元素:则a1有且只有一个后继;an有且只有一个前驱;ai有且只有一个前驱和一个后继。线性表是一种逻辑结构,定义了一组元素之间“一个接一个”的前后关系。根据这个结构在计算机内存中的存储方式:我们分为顺序存储结构和链式存储结构。顺序表适合查询多、修改少的场景。链表适合频繁插入删除、动态性强的场景。

weixin_45467593的博客 1433

数据结构--顺序表,链表,双向链表

所谓数据结构,拆开来讲,就是数据和结构。数据:所有能输入到计算机中并且能被程序识别和处理的符号集合,包括数值数据(整数,实数),非数值型数据(图形,声音,文字)。结构:数据之间的关系数据对象:相同特性的数据元素的集合数据元素:数据的基本单位数据项:数据元素的最小单位关系图:最基础的数据结构:数组。

2301_80190473的博客 927

【Python】数据结构——线性表(顺序表、单链表、循环单链表、双链表、循环双链表)

线性表是由若干个具有相同特性的数据元素组成的有限序列 空表就是线性表中不包含任何元素,长度为零。当线性表非空时,线性表长度即线性表内数据元素的个数。 直接前驱:线性表某一个元素的前一个元素 直接后继:线性表某一个元素的后一个元素

二琳爱吃肉的博客 3665

数据结构与算法 4.线性表(顺序表以及ArrayList源码、单向链表 双向链表以及LinkedList源码)

文章目录前言一.1.11.21.3二.2.12.22.3三.3.13.23.3 前言 前驱元素:若A元素在B元素的前面,则称A为B的前驱元素 后继元素:若B元素在A元素的后面,则称B为A的后继元素   线性表是最基本最常用的一种数据结构,一个线性表是n个具有相同特性的数据元素的有限序列; 线性表的特征:数据元素之间具有一对一的逻辑关系 第一个数据元素没有前驱,这个数据元素被称为头结点; 最后一个数据元素没有后继,这个数据元素被称为尾结点; 除了第一个和最后一个数据元素外,其他数据元素有且仅有一个前驱和一

不是一枚开发 5万+

单,双向链表详解

这章开始链表的详解,如果不了解数据结构和顺序表,可以看看-->

chuxinweihui的博客 1143

数据结构——线性表:顺序表、单向链表、循环链表、双向链表

线性表 是一种数据结构:n个数据元素的有限序列 表示形式: L = (a1,a2...an) a1是线性表的元素,小写。 n=0时候为空表 数据元素具有相同特性 相邻元素之间存在序偶关系:即有唯一的第一和最后一个元素,除了第一个元素外,每个元素有且只有一个前驱,除最后一个元素外,有且只有一个后继。 定义数据类型构成list: 怎么具体实现 存储结构:顺序、链式...

tyfwin的博客 1379

C数据结构--线性表(顺序表|单链表|双向链表)

ADT List{数据对象:D ={a;a:属于Elemset,(i=1,2…n,n≥0)}数据关系:R={< ai.1,a¡>|a¡-1,a;属于D,(i=2,3.,n))基本操作:……} ADT List线性表的顺序表示又称为顺序存储结构或者顺序映像。顺序存储的定义:把逻辑上相邻的数据元素存储在物理上相邻的存储单元中的存储结构。以元素在计算机内存中“物理位置相邻”来表示线性表中数据元素之间的逻辑关系。

RebrithMan的学习笔记 794
上一篇: C语言之聚合数据
下一篇: 数据结构之栈与队列
Liuqy-05
博客等级 码龄144天 1粉丝 35原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值