线性表及其应用C语言实现(数据结构复习最全笔记)(期末复习最新版)

引子

线性表

一、顺序表的表示与实现

注意我写的代码和课本的差不多,和PPT的风格还是有些区别的,但本质没啥区别其实,你会哪个都成

1.线性表的顺序结构定义

#define LIST_INIT_SIZE  100 //线性表存储空间的初始分配量
#define LISTINCREMENT   10 //线性表存储空间的分配增量
typedef struct
{
    ElemType* elem;   //存储空间基地址
    int length;       //表中元素的个数(表长)
    int listsize;     //表容量大小(以sizeof(ElemType)为单位)
} SqList;   //顺序表类型定义

2.顺序表的初始化(构建空的线性表)

Status InitList_Sq(SqList &L){
  //初始化L为一个空的有序顺序表
    L.elem = (ElemType *)malloc(LIST_INIT_SIZE*sizeof(ElemType));
    if(!L.elem)
        exit(OVERFLOW);//存储分配失败
    L.listsize = LIST_INIT_SIZE;//初始存储容量
    L.length = 0;//空表长度为0
    return OK;
}

3.顺序表的查找操作

int ListLocate_Sq(SqList L, ElemType e)
{
    int a = -1;//给a赋初值,无论查找的数据在第几个,都不可能是第-1个,所以赋值-1
    for(int i = 0;i<=L.length-1;i++)
    {
        if(L.elem[i] == e)
        {
            a = i;
            break;
        }
    }
    if(a>=0&&a<=L.length-1)
        return a+1;
    else
        return ERROR;
}

4.顺序表的插入操作 

注意第三步只能倒序向后移动不能向前(只能从后往前遍历不能反了),否则后面的值都会被盖住!!!

动态分配的代码: 

Status ListInsert_Sq(SqList &L, int pos, ElemType e)
{
    ElemType *newbase;
    if(pos>=1&&pos<=L.length+1)//注意这个范围
    {
        if(L.length>=L.listsize)//此时要扩容
        {
            newbase = (ElemType*)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType));
            if(!newbase)
                return ERROR;
            L.elem = newbase;//新分配空间的基地址
            L.listsize+=LISTINCREMENT;
        }
        //即将进行插入操作
        ElemType *p,*q;
        p = &(L.elem[pos-1]);//将原来pos位置元素的地址分配给指针p,即p为插入位置
        for(q = &(L.elem[L.length-1]);q>=p;--q)
            *(q+1) = *q;//将原来顺序表最后一个位置数据的地址分配给q,然后从后往前依次将数据向后移动一位
        *p = e;
        ++L.length;
        return OK;
    }
    else
        return OVERFLOW;
}

5.顺序表的删除操作 

同理这里则只能顺序向前移动

动态分配的代码:


Status ListDelete_Sq(SqList &L, int pos, ElemType &e)
{
    if(pos>=1&&pos<=L.length)//注意范围
    {
        ElemType *p;
        e = L.elem[pos-1];//先将e赋值,也就是返回删掉了哪个数,因为要返回引用
        for(p = &(L.elem[pos-1]);p<=&(L.elem[L.length-1]);p++)
            *p = *(p+1);
        --L.length;
        return OK;
    }
    else
        return OVERFLOW;
}

以下是做题时常考的地方!!!

6.顺序表常见的输出函数

void ListPrint_Sq(SqList L)
{
    for(int i = 0;i < L.length;i++)
    {
        if(i==0)
            printf("%d",L.elem[i]);
        else
            printf(" %d",L.elem[i]);
    }
}
void ListPrint_Sq(SqList L)
{
    ElemType *p = L.elem;//遍历元素用的指针
    for(int i = 0; i < L.length; ++i){
        if(i == L.length - 1){
            printf("%d", *(p+i));
        }
        else{
            printf("%d ", *(p+i));
        }
    }
}

7.顺序表的创建

Status ListCreate_Sq(SqList &L)
{
    //初始化
    L.elem = (ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType));
    if(!L.elem)
        return OVERFLOW;
    L.length = 0;
    L.listsize = LIST_INIT_SIZE;
    //输入信息创建顺序表
    int n;
    scanf("%d",&n);
    for(int i = 0;i < n;i++)
    {
        scanf("%d",&L.elem[i]);
        L.length++;
    }
    return OK;
}

8.顺序表的就地逆置

void ListReverse_Sq(SqList &L)
{
    int tmp;
    //int n;
    for(int i = 0;i< n/2;i++)
    {
        tmp = L.elem[i];
        L.elem[i]=L.elem[n-i-1];
        L.elem[n-i-1] = tmp;
    }
}

9.顺序表的合并

 <1>顺序表的无序合并

SqList bingji(SqList &S1,SqList &S2)
{
    SqList S;
    SqListInit(S);
    int i,j;
    for(i = 0;i<S1.length;i++)
    {
        S.elem[i] = S1.elem[i];
        S.length++;
    }
    for(j=0;j<S2.length;j++)
    {
        int flag = 1;
        for(int k = 0;k<S.length;k++)
        {
            if(S2.elem[j] == S.elem[k])
            {
                flag = 0;
                break;
            }
        }
        if(flag)
        {
            S.elem[i++] = S2.elem[j];
            S.length++;
        }
    }
    return S;

}

<2>顺序表的有序合并(升序)

void MergeList_sq(SqList La,SqList Lb,SqList &Lc)
{
    //实现代码
    pa = La.elem;pb = Lb.elem;
    Lc.listsize = Lc.length = La.length+Lb.length;
    pc = Lc.elem = (ElemType*)malloc(Lc.listsize*sizeof(ElemType));
    if(!Lc.elem)
        exit(OVERFLOW);
    pa_last = La.elem + La.length - 1;
    pb_last = Lb.elem + Lb.length - 1;
    while(pa<=pa_last&&pb<=pb_last)
    {
        if(*pa<=*pb)
            *pc++ = *pa++;
        else
            *pc++ = *pb++;
    }
    while(pa<=pa_last)
        *pc++ = *pa++;
    while(pb<=pb_last)
        *pc++ = *pb++;
    /*
    //算法大致代码
    InitList(Lc);
    i = j =1;k = 0;
    La_len = ListLength(La);
    Lb_Len = ListLength(Lb);
while(i<=La_len&&j<=Lb_len)
{
    GetElem(La,i,ai);GetElem(Lb,j,bj);
    if(ai<=bj)
    {
        ListInsert(Lc,++k,ai);
        i++;
    }
    else
    {
        ListInsert(Lc,++k,bj);
        j++;
    }
}
while(i<=La_len)
{
    GetElem(La,i,ai);
    ListInsert(Lc,++k,ai);
}
while(j<=Lb_len)
{
    GetElem(Lb,j,bj);
    ListInsert(Lc,++k,bj);
}
    */
}

10.顺序表的插入并排序且考虑扩容

Status ListInsert_SortedSq(SqList &L, ElemType e)
{
    ElemType *newbase;
    if(L.length >= L.listsize)//扩容
    {
        newbase = (ElemType*)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType));
        if(!newbase)
            exit(OVERFLOW);
        L.elem = newbase;
        L.listsize += LISTINCREMENT;
    }
    int i,j;
    for(i=0; e>*(L.elem+i)&&i<L.length; i++);//遍历单链表,找到插入位置
    for(j=L.length; j>i; j--)
    {
        *(L.elem+j)=*(L.elem+j-1);//元素往后移一个位置
    }
    *(L.elem+i)=e;
    L.length++;//增加链表长度
    return OK;
}




这里要补充一个知识:

关于链表和顺序表的插入删除操作对比!!!

单链表插入和删除的时间复杂度都是O(N)。如果我们不知道第i个结点的指针位置,此时单链表和顺序表在插入删除操作方面几乎无差别。但如果,我们希望从第i个位置,插入10个结点,对于顺序存储结构意味着,每一次插入都要移动n-i个结点,每次都是O(n)。而单链表,我们只需要在第一次时,找到第i个位置的指针,此时为O(N),接下来只是简单地通过指针赋值移动指针而已,时间复杂度都是O(1)!!!

显然,插入或删除操作频繁时,单链表的效率优势越明显!!!
 

二.(单)链表的表示与实现

1.单链表的链式结构定义

typedef struct LNode
{  
    ElemType data;  //数据域
    struct LNode *next; //指针域
}LNode,*LinkList; //循环单链表类型定义与单链表定义相同,区别在尾节点next取值

2.单链表的读取操作(获取第i个元素位置)

Status GetElem(LinkList &L,int i,ElemType &e)
{
    LinkList p = L->next;//p指向第一个结点
    int j = 1;//计数器j
    while(p&&j<i)
    {
        p = p->next;//就是遍历,往后挪
        j++;
    }
    if(j>i||!p)
        return 0;
    e = p->data;
    return OK;
}

注意这两个操作的顺序,先右后左,反了的话你就没插上 

3.单链表的插入操作

Status LinkListInsert(LinkList &L,int pos,ElemType e)
{
    LinkList p = L->next,s;
    int j = 1;
    while(p&&j<pos-1)
    {
        p = p->next;//让p指向下一个结点
        j++;
    }
    if(!p&&j>pos-1)
        return 0;
    s = (LinkList)malloc(sizeof(LNode));
    s->data = e;
    s->next =p->next;//这两部是插入操作的核心
    p->next =s;
    return 1;
}

不能直接p->next=p->next->next,因为这里有着一个内存空间释放的问题!!!所以最后肯定要free别忘了!!!

4.单链表的删除操作

Status ListDelete(LinkList &L,int i,ElemType &e)
{
    LinkList p = L->next,q;
    int j = 1;
    while(p&&j<i-1)
    {
        p = p->next;
        j++;
    }
    if(!p||j>i-1)
        return 0;
    q = p->next;//这两步是删除操作的核心
    p->next = q->next;
    e = q->data;
    free(q);//别忘了free
    return 1;
}

补充一个(循环)单链表的区间删除函数

原理一画图就明白了

void ListDelete_CL(LinkList &CL, ElemType min, ElemType max)
{
    LinkList p = CL->next,q;
    while(p->next!=CL)
    {
        q = p->next;
        if(q->data>min&&q->data<max)
        {
            p->next = q->next;//删除操作
            free(q);
        }
        else
            p = p->next;
    }
}

5.单链表的求表长操作

Status ListLength(LinkList &L)
{
    LinkList p = L->next;
    int j = 0;
    while(p)
    {
        p = p->next;
        j++;
    }
    return j;
}

以下是常考的地方

6.单链表常用打印操作

void ListPrint(LinkList &L)
{
    LinkList p = L->next;//p指向第一个元素结点
    int flag = 1;
    while(p!=NULL)
    {
        if(flag)
        {
            cout<<p->data;
            flag = 0;
        }
        else
        {
            cout<<" "<<p->data;
        }
        p = p->next;
    }
    cout<<endl;
}

7.单链表的判空操作

Status JudgeEmpty(LinkList &L)
{
    LinkList p = L->next;
    if(p)
        return false;
    else
        return true;
}

8.单链表的元素定位操作

Status ListLocate(LinkList &L,ElemType e)
{
    LinkList p = L->next;
    int j = 1;
    while(p&&e!=p->data)
    {
        p = p->next;
        j++;
    }
    if(!p)
        return 0;
    else
        return j;
}

8.单链表常用free函数

void ListFree(LinkList &L)//最好自己写free函数
{
    LinkList p = L->next;
    //LinkList rear;
    //rear = L;
    while(p)
    {
        free(p);
        //rear = p;
        p = p->next;
    }
    free(p);
}

9.单链表的整表创建

主要有两种方法:头插和尾插入(含义一定要理解清除)

a.头插法

Status ListCreate_CL(LinkList &CL,int n)
{
    LinkList p;
    int i;
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    for(int i = 0;i<n;i++)
    {
        p = (LinkList)malloc(sizeof(LNode));
        scanf("%d",&p->data);
        p->next = L->next;
        L->next = p;//插入到表头
    }
}

b.尾插法(正常思路,最常见)(两种)

Status ListCreate_L(LinkList &L,int n)
{
    LinkList rear,p;   //一个尾指针,一个指向新节点的指针
    L=(LinkList)malloc(sizeof (LNode));
    if(!L)exit(OVERFLOW);
    L->next=NULL;               //先建立一个带头结点的单链表
    rear=L;  //初始时头结点为尾节点,rear指向尾巴节点
    for (int i=1;i<=n;i++){  //每次循环都开辟一个新节点,并把新节点拼到尾节点后
        p=(LinkList)malloc(sizeof(LNode));//生成新结点
        if(!p)exit(OVERFLOW);
        scanf("%d",&p->data);//输入元素值
        p->next=NULL;  //最后一个节点的next赋空
        rear->next=p;
        rear=p;
    }
    return OK;
}

//或者

Status ListCreate_CL(LinkList &CL)
{
    CL = (LinkList)malloc(sizeof(LNode));
    //LinkList head = CL;
    LinkList rear = CL;//rear指向尾部的结点
    rear->next = NULL;
    int t;
    while(cin>>t&&t!=-1)
    {
        LinkList p = (LinkList)malloc(sizeof(LNode));
        p->data = t;
        rear->next = p;//把p插入尾部,指针移到最后
        rear = p;//数值移到最后(r的指针过去了,他的数值也得是p啊)
    }
    rear->next = NULL;//让最后的尾指针指向NULL,否则链表创建无法停止
    return 0;
}

注意关键的两句咋理解:

此外,补充一个循环单链表的整表创建(这里用尾插法实现,头插法我觉得是实现不了的)


//其实只有两步不同
Status ListCreate_CL(LinkList &CL)//尾插法创建循环单链表
{
    CL = (LinkList)malloc(sizeof(LNode));
    LinkList rear = CL;//第一个不同,尾指针指向头结点
    rear->next = NULL;
    int n;
    scanf("%d",&n);
    for(int i = 0;i < n;i++)
    {
        LinkList p = (LinkList)malloc(sizeof(LNode));
        int t;
        scanf("%d",&t);
        p->data = t;
        rear->next = p;
        rear = p;
    }
    rear->next = CL;//第二个不同最后再次指向头结点
    return OK;
}

10.单链表的整表删除

算法思路:

声明两个结点p和q

将第一个结点赋值给p

循环:

将下一个结点赋值给q

释放p

将q赋值给p

理解不了画个图啥都会了!!!

别忘了free哦

Status ClearList(LinkList &L)
{
    LinkList p = L->next,q;
    while(p)
    {
        q = p->next;
        free(p);
        q = p;
    }
    L->next = NULL;
    return OK;
}

11.单链表的就地逆置(较难)

算法思想:逆置链表初始为空,表中节点从原链表中依次“删除”,再逐个插入逆置链表的表头(即“头插”到逆置链表中),使它成为逆置链表的“新”的第一个结点,如此循环,直至原链表为空。

void ListReverse_L(LinkList &L)//L为头结点
{
    LinkList p = L->next,q;
    L->next = NULL;
    while(p)
    {
        //向后挪
        q = p;//
        p = p->next;
        //头插
        q->next = L->next;//非常重要,相当于p和q之间没有了指针连接
        L->next = q;//把q接到头的后面
    }
}


详细图解参考一位大佬的文章:单链表的就地逆置_v_xchen_v的博客-CSDN博客_对不带头结点的单链表进行就地逆置的算法

12.单链表的合并

void merge(LinkList La,LinkList Lb,LinkList &Lc){
    LinkList pa,pb,pc;
    pa=La->next;pb=Lb->next;
    Lc=pc=La;
    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);
}

 13.单链表的递归创建

Status ListCreate_L_Rec(LinkList &L,int n)
{
//递归边界:创建空表时只需将L赋空即可;
//递归关系:创建非空表时,将链表看做两部分:首元素组成的子表La, 第二个元素及其后元素构成的子表Lb。
//子表La容易创建(只需开辟一个节点)
//子表Lb由于规模小可以递归创建完成(类似数学归纳法的假设,只要小的都可以建设能完成)
//最后将两个子表拼接即可。
  LNode *La, *Lb;
  if(n==0) L=NULL;
  else{
    La=(LNode *)malloc(sizeof(LNode));  //开辟子表La
    if(!La) exit(OVERFLOW);
    scanf("%d",&La->data);
    ListCreate_L_Rec(Lb,n-1 ); //递归创建子表Lb
    La->next = Lb;  //两个子表拼结
    L=La;  //第一个子表的地址赋给L。请思考为什这样处理不会使得主函数中的L取错值?
  }
  return OK;
}

14.单链表的递归输出

方法1:递归法 

void ListPrint_L_Rec(LinkList L)
{
    if(!L)
        return;
    printf(" %d",L->data);
        ListPrint_L_Rec(L->next);
        return;
}

方法2:直接输出法

void ListPrint_L_Rec(LinkList L)
{
    LinkList p = L;
    //int flag = 1;
    while(p)
    {
        //int flag = 1;
            printf(" %d",p->data);
        p = p->next;
    }
    printf("\n");
}

三.循环链表

循环链表是另一种形式的链式存贮结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。

分类

循环链表

循环链表

(1)单循环链表——在单链表中,将终端结点的指针域NULL改为指向表头结点或开始结点即可。

(2)多重链的循环链表——将表中结点链在多个环上 。

空链判断

判断空链表的条件是

head==head->next;

rear==rear->next;

尾指针(意义重大,方便查找)

尾指针rear表示的单循环链表对开始结点a1和终端结点an查找时间都是O(1)。而表的操作常常是在表的首尾位置上进行,因此,实用中多采用尾指针表示单循环链表。带尾指针的单循环链表可见下图。

注意:判断空链表的条件为rear==rear->next;

两个循环链表的合并

特点

循环链表的特点是无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。

【例】在链表上实现将两个线性表(a1,a2,…,an)和(b1,b2,…,bm)连接成一个线性表(a1,…,an,b1,…bm)的运算。

分析:若在单链表或头指针表示的单循环表上做这种链接操作,都需要遍历第一个链表,找到结点an,然后将结点b1链到an的后面,其执行时间是O(n)。若在尾指针表示的单循环链表上实现,则只需修改指针,无须遍历,其执行时间是O(1)。

相应的算法如下:

LinkListConnect(LinkListA,LinkListB)

{//假设A,B为非空循环链表的尾指针

LinkListp=A->next;//①保存A表的头结点位置

A->next=B->next->next;//②B表的开始结点链接到A表尾

free(B->next);//③释放B表的头结点

B->next=p;//④

returnB;//返回新循环链表的尾指针

}

注意:

①循环链表中没有NULL指针。涉及遍历操作时,其终止条件就不再是像非循环链表那样判别p或p->next是否为空,而是判别它们是否等于某一指定指针,如头指针或尾指针等。

②在单链表中,从一已知结点出发,只能访问到该结点及其后续结点,无法找到该结点之前的其它结点。而在单循环链表中,从任一结点出发都可访问到表中所有结点,这一优点使某些运算在单循环链表上易于实现。

四.双向链表

参考博文:双链表 - kaizenly - 博客园

具体操作与单链表很相似,只有插入删除不同

循环链表与双向链表总结(!!!)

总结:

循环链表可以让我们从一个结点出发遍历所有节点

循环链表的判空条件p->next = head

此外,有时候不设立头指针而设立尾指针可以简化算法,例如两个表的合并

而双向链表的好处则在于对某一结点的前后结点操作带来了便利

但是他耗费了时间

带表头附加结点的双向循环链表为空的判断条件是头指针L满足条件(

  1. L->right= =L

)。

此外一个重点是它一般都有头指针而没有尾指针

相关做题技巧(无敌总结,考了n次!!!):

涉及到删除最后一个结点的,就要选择带头指针的,只有尾指针就解决不了了,因为删除最后一个结点需要找到他的直接前驱结点,肯定要遍历的,那就需要头指针。一般是选择带表头附加结点的双循环链表(时间更短),也可以选择给出表头指针的单循环链表(不可以是双向链表)

剩下的一般用带表尾指针的循环单链表即可

给一种情况的分析:(绝对考点!!!)

1.在最后一个元素之后插入元素和删除第一个元素

单链表(带头结点):

前者因为需要遍历全表,所以O(N)

后者直接修改指针,让头结点的指针指向第二个元素,O(1)

不带头结点的单循环链表:

前者与单链表相同,O(N)

后者可不一样,因为没有头结点,第一个元素的直接前驱为最后一个元素,而单链表的删除操作必须找到他的直接前驱,所以需要遍历全表才行,O(N)

注意:可见头结点是这类题的小坑

双链表:前后两者都与单链表相同,前者O(N),O(1)

不带头结点且有尾指针的单循环链表:看到这应该能发现尾指针的伟大意义,使前者后者的时间复杂度都变为O(1)

五.静态链表

 

一、顺序表的表示与实现

1.线性表的顺序结构定义

#define LIST_INIT_SIZE  100 //线性表存储空间的初始分配量
#define LISTINCREMENT   10 //线性表存储空间的分配增量
typedef struct
{
    ElemType* elem;   //存储空间基地址
    int length;       //表中元素的个数(表长)
    int listsize;     //表容量大小(以sizeof(ElemType)为单位)
} SqList;   //顺序表类型定义

2.顺序表的初始化(构建空的线性表)

Status InitList_Sq(SqList &L){
  //初始化L为一个空的有序顺序表
    L.elem = (ElemType *)malloc(LIST_INIT_SIZE*sizeof(ElemType));
    if(!L.elem)
        exit(OVERFLOW);//存储分配失败
    L.listsize = LIST_INIT_SIZE;//初始存储容量
    L.length = 0;//空表长度为0
    return OK;
}

3.顺序表的插入操作

Status ListInsert_Sq(SqList &L, int pos, ElemType e)
{
    ElemType *newbase;
    if(pos>=1&&pos<=L.length+1)//注意这个范围
    {
        if(L.length>=L.listsize)//此时要扩容
        {
            newbase = (ElemType*)realloc(L.elem,(L.listsize+LISTINCREMENT)*sizeof(ElemType));
            if(!newbase)
                return ERROR;
            L.elem = newbase;//新分配空间的基地址
            L.listsize+=LISTINCREMENT;
        }
        //即将进行插入操作
        ElemType *p,*q;
        p = &(L.elem[pos-1]);//将原来pos位置元素的地址分配给指针p,即p为插入位置
        for(q = &(L.elem[L.length-1]);q>=p;--q)
            *(q+1) = *q;//将原来顺序表最后一个位置数据的地址分配给q,然后从后往前依次将数据向后移动一位
        *p = e;
        ++L.length;
        return OK;
    }
    else
        return OVERFLOW;
}

4.顺序表的删除操作


Status ListDelete_Sq(SqList &L, int pos, ElemType &e)
{
    if(pos>=1&&pos<=L.length)//注意范围
    {
        ElemType *p;
        e = L.elem[pos-1];//先将e赋值,也就是返回删掉了哪个数,因为要返回引用
        for(p = &(L.elem[pos-1]);p<=&(L.elem[L.length-1]);p++)
            *p = *(p+1);
        --L.length;
        return OK;
    }
    else
        return OVERFLOW;
}

5.顺序表的查找操作

int ListLocate_Sq(SqList L, ElemType e)
{
    int a = -1;//给a赋初值,无论查找的数据在第几个,都不可能是第-1个,所以赋值-1
    for(int i = 0;i<=L.length-1;i++)
    {
        if(L.elem[i] == e)
        {
            a = i;
            break;
        }
    }
    if(a>=0&&a<=L.length-1)
        return a+1;
    else
        return ERROR;
}

6.顺序表常见的输出函数

void ListPrint_Sq(SqList L)
{
    ElemType *p = L.elem;//遍历元素用的指针
    for(int i = 0; i < L.length; ++i){
        if(i == L.length - 1){
            printf("%d", *(p+i));
        }
        else{
            printf("%d ", *(p+i));
        }
    }
}

7.顺序表的就地逆置

void ListReverse_Sq(SqList &L)
{
    int tmp;
    //int n;
    for(int i = 0;i< n/2;i++)
    {
        tmp = L.elem[i];
        L.elem[i]=L.elem[n-i-1];
        L.elem[n-i-1] = tmp;
    }
}

8.顺序表的合并

 <1>顺序表的无序合并

SqList bingji(SqList &S1,SqList &S2)
{
    SqList S;
    SqListInit(S);
    int i,j;
    for(i = 0;i<S1.length;i++)
    {
        S.elem[i] = S1.elem[i];
        S.length++;
    }
    for(j=0;j<S2.length;j++)
    {
        int flag = 1;
        for(int k = 0;k<S.length;k++)
        {
            if(S2.elem[j] == S.elem[k])
            {
                flag = 0;
                break;
            }
        }
        if(flag)
        {
            S.elem[i++] = S2.elem[j];
            S.length++;
        }
    }
    return S;

}

<2>顺序表的有序合并(升序)

void MergeList_sq(SqList La,SqList Lb,SqList &Lc)
{
    //实现代码
    pa = La.elem;pb = Lb.elem;
    Lc.listsize = Lc.length = La.length+Lb.length;
    pc = Lc.elem = (ElemType*)malloc(Lc.listsize*sizeof(ElemType));
    if(!Lc.elem)
        exit(OVERFLOW);
    pa_last = La.elem + La.length - 1;
    pb_last = Lb.elem + Lb.length - 1;
    while(pa<=pa_last&&pb<=pb_last)
    {
        if(*pa<=*pb)
            *pc++ = *pa++;
        else
            *pc++ = *pb++;
    }
    while(pa<=pa_last)
        *pc++ = *pa++;
    while(pb<=pb_last)
        *pc++ = *pb++;
    /*
    //算法大致代码
    InitList(Lc);
    i = j =1;k = 0;
    La_len = ListLength(La);
    Lb_Len = ListLength(Lb);
while(i<=La_len&&j<=Lb_len)
{
    GetElem(La,i,ai);GetElem(Lb,j,bj);
    if(ai<=bj)
    {
        ListInsert(Lc,++k,ai);
        i++;
    }
    else
    {
        ListInsert(Lc,++k,bj);
        j++;
    }
}
while(i<=La_len)
{
    GetElem(La,i,ai);
    ListInsert(Lc,++k,ai);
}
while(j<=Lb_len)
{
    GetElem(Lb,j,bj);
    ListInsert(Lc,++k,bj);
}
    */
}

<2>(单)链表的表示与实现

1.单链表的链式结构定义

typedef struct LNode
{  
    ElemType data;  //数据域
    struct LNode *next; //指针域
}LNode,*LinkList; //循环单链表类型定义与单链表定义相同,区别在尾节点next取值

2.单链表的读取操作(获取第i个元素位置)

Status GetElem(LinkList &L,int i,ElemType &e)
{
    LinkList p = L->next;//p指向第一个结点
    int j = 1;//计数器j
    while(p&&j<i)
    {
        p = p->next;//就是遍历,往后挪
        j++;
    }
    if(j>i||!p)
        return 0;
    e = p->data;
    return OK;
}

3.单链表的插入操作

Status LinkListInsert(LinkList &L,int pos,ElemType e)
{
    LinkList p = L->next,s;
    int j = 1;
    while(p&&j<pos-1)
    {
        p = p->next;
        j++;
    }
    if(!p&&j>pos-1)
        return 0;
    s = (LinkList)malloc(sizeof(LNode));
    s->data = e;
    s->next =p->next;//这两部是插入操作的核心
    p->next =s;
    return 1;
}

4.单链表的删除操作

Status ListDelete(LinkList &L,int i,ElemType &e)
{
    LinkList p = L->next,q;
    int j = 1;
    while(p&&j<i-1)
    {
        p = p->next;
        j++;
    }
    if(!p||j>i-1)
        return 0;
    q = p->next;//这两步是删除操作的核心
    p->next = q->next;
    e = q->data;
    free(q);//别忘了free
    return 1;
}

5.单链表的求表长操作

Status ListLength(LinkList &L)
{
    LinkList p = L->next;
    int j = 0;
    while(p)
    {
        p = p->next;
        j++;
    }
    return j;
}

6.单链表常用打印操作

void ListPrint(LinkList &L)
{
    LinkList p = L->next;//p指向第一个元素结点
    int flag = 1;
    while(p!=NULL)
    {
        if(flag)
        {
            cout<<p->data;
            flag = 0;
        }
        else
        {
            cout<<" "<<p->data;
        }
        p = p->next;
    }
    cout<<endl;
}

7.单链表的判空操作

Status JudgeEmpty(LinkList &L)
{
    LinkList p = L->next;
    if(p)
        return false;
    else
        return true;
}

8.单链表的元素定位操作

Status ListLocate(LinkList &L,ElemType e)
{
    LinkList p = L->next;
    int j = 1;
    while(p&&e!=p->data)
    {
        p = p->next;
        j++;
    }
    if(!p)
        return 0;
    else
        return j;
}

8.单链表常用free函数

void ListFree(LinkList &L)//最好自己写free函数
{
    LinkList p = L->next;
    //LinkList rear;
    //rear = L;
    while(p)
    {
        free(p);
        //rear = p;
        p = p->next;
    }
    free(p);
}

9.单链表的整表创建

a.头插法

Status ListCreate_CL(LinkList &CL,int n)
{
    LinkList p;
    int i;
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    for(int i = 0;i<n;i++)
    {
        p = (LinkList)malloc(sizeof(LNode));
        scanf("%d",&p->data);
        p->next = L->next;
        L->next = p;//插入到表头
    }
}

b.尾插法(正常思路,最常见)(两种)

Status ListCreate_L(LinkList &L,int n)
{
    LinkList rear,p;   //一个尾指针,一个指向新节点的指针
    L=(LinkList)malloc(sizeof (LNode));
    if(!L)exit(OVERFLOW);
    L->next=NULL;               //先建立一个带头结点的单链表
    rear=L;  //初始时头结点为尾节点,rear指向尾巴节点
    for (int i=1;i<=n;i++){  //每次循环都开辟一个新节点,并把新节点拼到尾节点后
        p=(LinkList)malloc(sizeof(LNode));//生成新结点
        if(!p)exit(OVERFLOW);
        scanf("%d",&p->data);//输入元素值
        p->next=NULL;  //最后一个节点的next赋空
        rear->next=p;
        rear=p;
    }
    return OK;
}

//或者

Status ListCreate_CL(LinkList &CL)
{
    CL = (LinkList)malloc(sizeof(LNode));
    //LinkList head = CL;
    LinkList rear = CL;
    rear->next = NULL;
    int t;
    while(cin>>t&&t!=-1)
    {
        LinkList p = (LinkList)malloc(sizeof(LNode));
        p->data = t;
        rear->next = p;//把p插入尾部,指针移到最后
        rear = p;//数值移到最后
    }
    rear->next = NULL;//让最后的尾指针指向NULL,否则链表创建无法停止
    return 0;
}

10.单链表的整表删除

算法思路:

声明两个结点p和q

将第一个结点赋值给p

循环:

将下一个结点赋值给q

释放p

将q赋值给p

Status ClearList(LinkList &L)
{
    LinkList p = L->next,q;
    while(p)
    {
        q = p->next;
        free(p);
        q = p;
    }
    L->next = NULL;
    return OK;
}

11.单链表的就地逆置(较难)

算法思想:逆置链表初始为空,表中节点从原链表中依次“删除”,再逐个插入逆置链表的表头(即“头插”到逆置链表中),使它成为逆置链表的“新”的第一个结点,如此循环,直至原链表为空。

void ListReverse_L(LinkList &L)//L为头结点
{
    LinkList p,q;
    p = L->next;
    L->next = NULL;
    while(p)
    {
        //向后挪
        q = p;//
        p = p->next;
        //头插
        q->next = L->next;//非常重要,相当于p和q之间没有了指针连接
        L->next = q;//把q接到头的后面
    }
}


详细图解参考一位大佬的文章:单链表的就地逆置_v_xchen_v的博客-CSDN博客_对不带头结点的单链表进行就地逆置的算法

12.单链表的合并

void merge(LinkList La,LinkList Lb,LinkList &Lc){
    LinkList pa,pb,pc;
    pa=La->next;pb=Lb->next;
    Lc=pc=La;
    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);
}

 13.单链表的递归创建

Status ListCreate_L_Rec(LinkList &L,int n)
{
//递归边界:创建空表时只需将L赋空即可;
//递归关系:创建非空表时,将链表看做两部分:首元素组成的子表La, 第二个元素及其后元素构成的子表Lb。
//子表La容易创建(只需开辟一个节点)
//子表Lb由于规模小可以递归创建完成(类似数学归纳法的假设,只要小的都可以建设能完成)
//最后将两个子表拼接即可。
  LNode *La, *Lb;
  if(n==0) L=NULL;
  else{
    La=(LNode *)malloc(sizeof(LNode));  //开辟子表La
    if(!La) exit(OVERFLOW);
    scanf("%d",&La->data);
    ListCreate_L_Rec(Lb,n-1 ); //递归创建子表Lb
    La->next = Lb;  //两个子表拼结
    L=La;  //第一个子表的地址赋给L。请思考为什这样处理不会使得主函数中的L取错值?
  }
  return OK;
}

14.单链表的递归输出

方法1:递归法 

void ListPrint_L_Rec(LinkList L)
{
    if(!L)
        return;
    printf(" %d",L->data);
        ListPrint_L_Rec(L->next);
        return;
}

方法2:直接输出法

void ListPrint_L_Rec(LinkList L)
{
    LinkList p = L;
    //int flag = 1;
    while(p)
    {
        //int flag = 1;
            printf(" %d",p->data);
        p = p->next;
    }
    printf("\n");
}

之后还会更新静态链表,循环链表,双向链表的笔记。

如果哪里代码有问题欢迎指正

  • 39
    点赞
  • 219
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值