王道数据结构代码实现(C++)

前言:基于王道书本的C++代码实现,配上流程图,它可以很好的帮助我们理清代码逻辑。并会加入一些自己的理解,以及易错点注解。

目录

第二章 线性表-常规代码

一、顺序表

1.顺序表存储结构定义

2.在顺序表L的第i个位置插入值为e的元素(1<=i<=L.length+1)

3. 删除表中第i个位置的元素,并引用变量e返回(1<=i<=L.length)

 4.相关代码实现及运行截图

二、链表(LinkList)

1.链表的定义、单链表基本操作

2.双链表


第二章 线性表-常规代码

一、顺序表

1.顺序表存储结构定义

2.在顺序表L的第i个位置插入值为e的元素(1<=i<=L.length+1)

1) j=i-1时,跳出循环,因为第i个元素的下标是i-1,移动完毕;

2)一定记得修改表长度!length

3)注意for循环的流程图,循环变量处于闭环中,开始画错了

3. 删除表中第i个位置的元素,并引用变量e返回(1<=i<=L.length)

 注意:先将被删除元素的值赋值给e保存,否则删除后元素值没了

 4.相关代码实现及运行截图

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

using namespace std;

//顺序表的定义
#define Maxsize 50
typedef struct{
    int data[Maxsize];
    int length;
}Sqlist;

//初始化顺序表
bool InitList(Sqlist &L){
    L.length=0;
    return true;
}

//判断顺序表是否为空
bool ListEmpty(Sqlist &L){
    if(L.length==0)
        return true;
    else
        return false;
}

//求表长,即元素个数
void ListLength(Sqlist &L){
    cout<<"当前表中元素个数为:"<<L.length<<endl;
}

//在L的第i个位置(位序)插入值为e的数据元素
bool ListInsert(Sqlist &L,int i,int e){
    if(i<1 ||i>L.length+1)//判断i的合法性
        return false;
    if(L.length>=Maxsize)//判断表是否已满
        return false;
    for(int j=L.length;j>=i;j--){
        L.data[j]=L.data[j-1];
    }
    L.data[i-1]=e;
    L.length++;
    return true;
}

//打印列表
void PrintL(Sqlist &L){
    cout<<"表中当前的数据是:";
    for(int i=0;i<L.length;i++)
        cout<<L.data[i]<<" ";
    cout<<endl;
}

//用e返回第i个元素的值
void GetElem(Sqlist L,int i){
    cout<<"当前表中第"<<i<<"个位置的元素值为:"<<L.data[i-1];
}

//删除第i个位置的数据元素,并用e返回
bool ListDelete(Sqlist &L,int i,int &e){
    if(i<1||i>L.length)
        return false;
    if(L.length==0)
        return false;
    e=L.data[i-1];
    for(int j=i;j<L.length;j++)
        L.data[j-1]=L.data[j];
    L.length--;
    return true;
}

//返回第一个与e相等的元素的位序(查找值为e的元素的位序)
int LocateElem(Sqlist &L,int e){
    for(int i=0;i<L.length;i++){
        if(L.data[i]==e)
            return i+1;
    }
    return false;
}



int main()
{
    Sqlist L;
    int i,e=10;
    InitList(L);//初始化顺序表
    ListEmpty(L);//判断顺序表是否为空
    cout<<"初始化结果是:"<<InitList(L)<<endl;
    cout<<"判空结果是:"<<ListEmpty(L)<<endl;

    cout<<endl<<"在表的第1个位置插入9:"<<endl;
    ListInsert(L,1,9);//在线性表的第1个位置插入9
    ListLength(L);
    PrintL(L);

    cout<<endl<<"依次插入10~14后:"<<endl;
    //依次插入10~14(批量插入规律的数字)
    for(i=2;i<6;i++)
    {
        ListInsert(L,i,e);
        e++;
    }
    ListLength(L);
    PrintL(L);//9 10 11 12 13

    //接下来插入一些不规律的数据
    cout<<endl<<"再插入一些其他数据"<<endl;
    ListInsert(L,3,66);
    ListInsert(L,6,-88);
    ListInsert(L,2,-3);
    ListInsert(L,5,24);
    ListLength(L);
    PrintL(L);//9 -3 10 66 24 11 12 -88 13 至此,插入完成
    GetElem(L,8);

    cout<<endl;
    int x;
    ListDelete(L,4,x);
    cout<<endl<<"'删除第4个元素'"<<endl;
    cout<<"删除的值为:"<<x<<endl;
    PrintL(L);

    cout<<"'查找值为12的元素位序'"<<endl;
    cout<<"值为12的元素位序为:"<<LocateElem(L,12)<<endl;
}

 图1、图2为运行截图

二、链表(LinkList)

1.链表的定义、单链表基本操作

(1)存储结构定义

写法1:好久没看,都想不起来,单链表定义中的*LinkList是什么意思了?

typedef struct LNode{
    int data;//数据域
    struct LNode* next;//指针域:指向LNode的指针
}LNode,*LinkList;

其实,这里的写法一与写法2等价,*LinkList其实就是定义了一个名为LinkList的指向struct LNode结构体类型变量的指针,把它看成struct LNode* LinkList会更加直观。有了它,我们就可以方便的定义一个单链表L,本质上和next一样,都是指向结点的指针,这个理解很关键。

LinkList L;    //等价于 struct LNode * L

写法2:

//单链表存储定义
struct LNode{
    int data;//数据域
    struct LNode* next;//指针域:指向LNode的指针
};
typedef struct LNode LNode;
typedef struct LNode* LinkList;//指向LNode的指针

(2)头插法建立单链表【逆序】List_HeadInsert(LinkList &L)

注意:这里有一个易错点,由于这是带头节点的单链表,初始化空表后,其实有一个节点,只不过它指向NULL,开始误写为s->next=L;L->next=s,但这应该属于不带头节点的写法;

正确的应该是s->next=L-next;虽然此时L->next指向空,可能会感觉找不到谁赋值给s->next,但是这里可以把NULL虚拟的看成一个结点,如上图所示,就很好理解啦。在链表里有了结点后,其他结点的插入也是如此。且最后一个结点的next肯定是指向NULL的。

(3)尾插法建立单链表【正序】List_TailInsert(LinkList &L)

注意:1.尾插法增加表尾指针r的目的:改链时能知道上一个结点的地址;

2.表尾指针初始时指向L;每插入一个结点的数据后要及时更新表尾指针r;不再插入结点后,需要将尾指针置空。

3.对于r->next=s;r=s两句的理解:即不断在表尾插入结点,然后更新表尾指针,最后当不再插入数据时,需要将尾结点(r->next)置空哦!

(4)按序号查找结点值:GetElem(LinkList L,int i):从第1个接结点开始,沿着指针next域逐个往后搜索,直到找到第i个结点为止,否则返回最后一个结点指针域NULL.

 注:1.i无效的条件时i<1,不是i<0.

2.跳出循环的条件:要么p指针移动到表尾,p=NULL;要么j=i,找到第i个结点。

相关代码及运行截图:

#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <typeinfo>

using namespace std;

//单链表存储定义-写法1
typedef struct LNode{
    int data;//数据域
    struct LNode* next;//指针域:指向LNode的指针
}LNode,*LinkList;

//头插法插入结点
LinkList List_HeadInsert(LinkList &L){
    LNode* s;//定义指向插入节点的指针、接收插入数据的变量
    int x;
    L=(LinkList)malloc(sizeof(LNode));//创建头节点
    L->next=NULL;//初始化为空表
    cin>>x;
    while(x!=10000){
        s=(LNode*)malloc(sizeof(LNode));
        s->data=x;
        s->next=L->next;//改链操作
        L->next=s;
        cin>>x;
    }
    return L;
}

//尾插法插入节点
LinkList List_TailInsert(LinkList &L){
    int x;
    L=(LinkList)malloc(sizeof(LNode));
    LNode* s;
    LNode* r=L;//表尾指针指向L
    L->next=NULL;
    cin>>x;
    while(x!=9999){
        s=(LNode*)malloc(sizeof(LNode));
        s->data=x;
        s->next=NULL;
        r->next=s;
        r=s;
        cin>>x;
    }
    r->next=NULL;
    return L;
}


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

//按值查找表结点
LNode* GetElem(LinkList L,int i){
    int j=1;
    LNode* p=L->next;
    if(i==0)
        return L;
    if(i<1)
        return NULL;
    while(p&&j<i){
        p=p->next;
        j++;
    }
    return p;
}

//顺序打印当前表中的数据
void Print(LinkList L){
    LNode* p;
    p=L->next;
    cout<<"当前表中的数据为:"<<endl;
    while(p!=NULL){
        cout<<p->data<<" ";
        p=p->next;
    }
}

int main(){
    LinkList L1,L2;//定义名为L1、L2的单链表
    cout<<"头插法插入结点,请输入数据:(输入10000结束)"<<endl;
    List_HeadInsert(L1);
    cout<<"L1当前的表长为:"<<ListLength(L1)<<endl;
    Print(L1);
    cout<<endl;

    cout<<endl<<"尾插法插入结点,请输入数据:(输入9999结束)"<<endl;
    List_TailInsert(L2);
    cout<<"L2当前的表长为:"<<ListLength(L2)<<endl;
    Print(L2);
    cout<<endl;

    cout<<endl<<"表L1的第2个结点的值为:"<<GetElem(L1,2)->data<<endl;
    cout<<"表L2的第4个结点的值为:"<<GetElem(L2,4)->data<<endl;

}

运行截图:

(5)按值找表中节点 LocateElem(LinkList L,int e)

 

(6)插入节点 ListInsert(LinkList &L,int x,int i);将值为x的新结点插入到第i个位置上

注意:由于之前已有按照序号查找结点的函数GetElem(LinkList L,int i),直接调用找到第i个结点的前驱结点,即可快速进行改链操作。注意改链前,需要先创建结点,并用s保存结点地址,以存入数据,和后续进行改链操作

(7)删除节点

(8)求表长

 注意:求表长时,若p指向第一个结点,则计数变量j的初值为0

(1)~(8)完整代码及运行结果如下:

#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <typeinfo>

using namespace std;

//单链表存储定义-写法1
typedef struct LNode{
    int data;//数据域
    struct LNode* next;//指针域:指向LNode的指针
}LNode,*LinkList;

//头插法插入结点
LinkList List_HeadInsert(LinkList &L){
    LNode* s;//定义指向插入节点的指针、接收插入数据的变量
    int x;
    L=(LinkList)malloc(sizeof(LNode));//创建头节点
    L->next=NULL;//初始化为空表
    cin>>x;
    while(x!=10000){
        s=(LNode*)malloc(sizeof(LNode));
        s->data=x;
        s->next=L->next;//改链操作
        L->next=s;
        cin>>x;
    }
    return L;
}

//尾插法插入节点
LinkList List_TailInsert(LinkList &L){
    int x;
    L=(LinkList)malloc(sizeof(LNode));
    LNode* s;
    LNode* r=L;//表尾指针指向L
    L->next=NULL;
    cin>>x;
    while(x!=9999){
        s=(LNode*)malloc(sizeof(LNode));
        s->data=x;
        s->next=NULL;
        r->next=s;
        r=s;
        cin>>x;
    }
    r->next=NULL;
    return L;
}


void ListLength(LinkList &L){
    LNode* p;
    int j=0;
    p=L->next;
    while(p){
        p=p->next;
        j++;
    }
    cout<<"表当前的长度为:"<<j<<endl;
    //return j;
}

//按序号查找表结点
LNode* GetElem(LinkList L,int i){
    int j=1;
    LNode* p=L->next;
    if(i==0)
        return L;
    if(i<1)
        return NULL;
    while(p&&j<i){
        p=p->next;
        j++;
    }
    return p;
}

//顺序打印当前表中的数据
void Print(LinkList L){
    LNode* p;
    p=L->next;
    cout<<"当前表中的数据为:"<<endl;
    while(p!=NULL){
        cout<<p->data<<" ";
        p=p->next;
    }
    cout<<endl;
}

//按值查找表结点,并返回结点p
LNode* LocateElem(LinkList L,int e)
{
    LNode* p=L->next;//指向头节点
    while(p!=NULL&&p->data!=e){
        p=p->next;
    }
    return p;
}

bool ListInsert(LinkList &L,int x,int i){
    LNode *p,*s;
    if(i<1)
        return false;
    p=GetElem(L,i-1);
    if(p==NULL)
        return false;
    s=(LNode*)malloc(sizeof(LNode));
    s->data=x;
    s->next=p->next;
    p->next=s;
    return true;
}

int ListDelete(LinkList &L,int i){
    LNode *p,*q;
    int e;
    if(i<1)
        return -1;//i的范围不合法,则返回错误标志-1;
    p=GetElem(L, i-1);//p指向待删结点的前驱节点
    q=p->next;//q指向待删节点;
    e=q->data;//保存待删数据
    p->next=q->next;//改链操作
    free(q);//释放被删结点的存储空间
    return e;
}


int main(){
    LinkList L1,L2;//定义名为L1、L2的单链表
    int e;
    cout<<"【1-头插法创建表L1】"<<endl;
    cout<<"头插法插入结点,请输入数据:(输入10000结束)"<<endl;
    List_HeadInsert(L1);
    ListLength(L1);
    Print(L1);
    
    cout<<endl<<"【2-尾插法创建表L2】"<<endl;
    cout<<"尾插法插入结点,请输入数据:(输入9999结束)"<<endl;
    List_TailInsert(L2);
    ListLength(L2);
    Print(L2);

    cout<<endl<<"【3-按照序号查找表结点的数据】"<<endl;
    cout<<"表L1的第2个结点的值为:"<<GetElem(L1,2)->data<<endl;
    cout<<"表L2的第4个结点的值为:"<<GetElem(L2,4)->data<<endl;

    cout<<endl<<"【4-按值查找结点】"<<endl;
    cout<<"请输入想要在L1中查找的数据:";
    cin>>e;
    cout<<"数据e的结点地址为:"<<LocateElem(L1,e)<<endl;
    cout<<"请输入想要在L2中查找的数据:";
    cin>>e;
    cout<<"数据e的结点地址为:"<<LocateElem(L2,e)<<endl;

    cout<<endl<<"【5-在表L第i个位置插入数据x】"<<endl;
    cout<<"在L1的第3个位置插入100"<<endl;
    ListInsert(L1,100,3);
    Print(L1);
    ListLength(L1);
    cout<<endl<<"在L2的第5个位置插入-999"<<endl;
    ListInsert(L2,-999,5);
    Print(L2);
    ListLength(L2);

    cout<<endl<<"【6-删除表L第i个位置的数据,并返回其数据值】"<<endl;
    cout<<"删除L1第2个位置的数据,其值为:"<<ListDelete(L1,2)<<endl;
    Print(L1);
    ListLength(L1);
    cout<<"删除L2第2个位置的数据,其值为:"<<ListDelete(L2,2)<<endl;
    Print(L2);
    ListLength(L2);
}

开始运行...
【1-头插法创建表L1】
头插法插入结点,请输入数据:(输入10000结束)
1 2 3 4 5 6 7 8 9 10000
表当前的长度为:9
当前表中的数据为:
9 8 7 6 5 4 3 2 1 

【2-尾插法创建表L2】
尾插法插入结点,请输入数据:(输入9999结束)
88 66 99 520 77 666 22 1314 9999
表当前的长度为:8
当前表中的数据为:
88 66 99 520 77 666 22 1314 

【3-按照序号查找表结点的数据】
表L1的第2个结点的值为:8
表L2的第4个结点的值为:520

【4-按值查找结点】
请输入想要在L1中查找的数据:7
数据e的结点地址为:0x1d087b0
请输入想要在L2中查找的数据:520
数据e的结点地址为:0x1d08890

【5-在表L第i个位置插入数据x】
在L1的第3个位置插入100
当前表中的数据为:
9 8 100 7 6 5 4 3 2 1 
表当前的长度为:10

在L2的第5个位置插入-999
当前表中的数据为:
88 66 99 520 -999 77 666 22 1314 
表当前的长度为:9

【6-删除表L第i个位置的数据,并返回其数据值】
删除L1第2个位置的数据,其值为:8
当前表中的数据为:
9 100 7 6 5 4 3 2 1 
表当前的长度为:9
删除L2第2个位置的数据,其值为:66
当前表中的数据为:
88 99 520 -999 77 666 22 1314 
表当前的长度为:8

运行结束。

2.双链表

1.双链表VS单链表最大不同:每个结点多了prior前驱指针域,优点主要是:使得找当前结点的前驱结点变得容易;不过在【插入】和【删除】的改链操作时,注意顺序前驱链接

2.【单链表】是【双链表】的基础,基于单链表,大约30min左右就可以快速构建起双链表的完整体系。其实就是一种【知识迁移】【融会贯通】【对比学习】的技能提升

  • 用尾插法创建初始化的双链表;(包含头节点创建、尾指针指向和更新、结点插入);
  • 按照序号查找结点值;
  • 利用前驱、后继指针访问当前结点的上一个数据、下一个数据;
  • 常规操作:双链表中的结点插入;
  • 常规操作:双链表中的结点删除;
#include <cstdlib>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <typeinfo>

using namespace std;

//测试用例:1 2 3 4 5 6 7 8 9 9999
//2 0
//4 0
//6 0


typedef struct DNode{
    int data;//数据
    DNode *prior,*next;//前驱和后继指针;
}DNode,*DLinkList;
//等价于 typedef struct DNode DNode;
//等价于 typedef struct DNode* DLinkList;


//尾插法创建初始双链表
DLinkList DList_TailInsert(DLinkList &DL){
    int x;
    DL=(DLinkList)malloc(sizeof(DLinkList));
    DNode* s;
    DNode* r=DL;//表尾指针指向L
    DL->next=NULL;
    cin>>x;
    while(x!=9999){
        s=(DNode*)malloc(sizeof(DNode));
        s->data=x;
        s->next=NULL;
        s->prior=r;//记得加上s的前驱
        r->next=s;
        r=s;
        cin>>x;
    }
    r->next=NULL;
    return DL;
}


//按位置查找结点
DNode* GetElem(DLinkList DL,int i){
    int j=1;
    DNode *p=DL->next;
    if(i==0)
        return DL;
    if(i<1)
        return NULL;
    while(p&&j<i){
        p=p->next;
        j++;
    }
    return p;
}

//双链表插入
bool DListInsert(DLinkList &DL,int i,int x){
    DNode *p,*s;
    if(i<1)
        return false;
    s=(DNode*)malloc(sizeof(DNode));
    s->data=x;
    p=GetElem(DL,i-1);
    s->next=p->next;
    p->next->prior=s;
    s->prior=p;
    p->next=s;
    return true;
}

//顺序打印当前表中的数据
void Print(DLinkList DL){
    DNode* p;
    p=DL->next;
    cout<<"当前表中的数据为:"<<endl;
    while(p!=NULL){
        cout<<p->data<<" ";
        p=p->next;
    }
    cout<<endl;
}

int DListDelete(DLinkList &L,int i){
    DNode *p,*q;//p为待删的前驱结点,q为待删结点
    int e;
    if(i<1)
        return -1;//i的范围不合法,则返回错误标志-1;
    p=GetElem(L, i-1);//p指向待删结点的前驱节点
    q=p->next;//q指向待删节点;
    q->prior=p;
    e=q->data;//保存待删数据
    p->next=q->next;//改链操作
    q->next->prior=p;
    free(q);//释放被删结点的存储空间
    return e;
}



int main()
{
    DLinkList DL;//创建名为DL的双链表;
    cout<<"【1-创建名为DL的双链表,输入9999结束】"<<endl;
    DList_TailInsert(DL);

    cout<<endl<<"【2-按序号查找DL中的数据】"<<endl;
    cout<<"第5个数据为:"<<GetElem(DL,5)->data<<endl;

    cout<<endl<<"【3-用前驱、后继指针访问前后】"<<endl;
    cout<<"它的前一个数据为:"<<GetElem(DL,5)->prior->data<<endl;
    cout<<"它的后一个数据为:"<<GetElem(DL,5)->next->data<<endl;

    cout<<endl<<"【4-在DL的第i个位置插入x】"<<endl;
    int i,x;
    cout<<"输入i和x(空格分隔),输入-1停止插入:";
    cin>>i>>x;
    while(i!=-1){
        DListInsert(DL,i,x);
        cout<<"输入i和x(空格分隔):";
        cin>>i;
        if(i!=-1)
            cin>>x;
    }
    Print(DL);

    cout<<endl<<"【5-删除DL第j个位置的数据】"<<endl;
    int j;
    cout<<"输入j(输入-1停止删除):";
    cin>>j;
    cout<<"删除的数据为:"<<DListDelete(DL,j)<<endl;
    Print(DL);
    while(j!=-1){
        cout<<"输入j:";
        cin>>j;
        cout<<"删除的数据为:"<<DListDelete(DL,j)<<endl;
        Print(DL);
    }
}

运行结果:(不好截图,直接复制过来啦)

开始运行...
【1-创建名为DL的双链表,输入9999结束】
1 2 3 4 5 6 7 8 9 9999

【2-按序号查找DL中的数据】
第5个数据为:5

【3-用前驱、后继指针访问前后】
它的前一个数据为:4
它的后一个数据为:6

【4-在DL的第i个位置插入x】
输入i和x(空格分隔),输入-1停止插入:2 0
输入i和x(空格分隔):4 0
输入i和x(空格分隔):6 0
输入i和x(空格分隔):-1
当前表中的数据为:
1 0 2 0 3 0 4 5 6 7 8 9 

【5-删除DL第j个位置的数据】
输入j(输入-1停止删除):2
删除的数据为:0
当前表中的数据为:
1 2 0 3 0 4 5 6 7 8 9 
输入j:3
删除的数据为:0
当前表中的数据为:
1 2 3 0 4 5 6 7 8 9 
输入j:4
删除的数据为:0
当前表中的数据为:
1 2 3 4 5 6 7 8 9 
输入j:-1
删除的数据为:-1
当前表中的数据为:
1 2 3 4 5 6 7 8 9 

运行结束。

耶!线性表这章终于结束啦!敲完代码感觉透透的啦!

内含资源如下: 1.基本数据结构 1.1.Array ........... 动态数组 1.2.LinkedList ... 链表 1.3.BST .............. 二分搜索树 1.4.MapBST ..... 二分搜索树(用于实现映射) 1.5.AVLTree ...... AVL树 2.接口 2.1.Queue ........... 队列接口 2.2.Stack .............. 栈接口 2.3.Set .................. 集合接口 2.4.Map ............... 映射接口 2.5.Merger .......... 自定义函数接口 2.6.UnionFind ..... 并查集接口 3.高级数据结构 3.1.ArrayQueue .......................... 队列_基于动态数组实现 3.2.LinkedListQueue .................. 队列__基于链表实现 3.3.LoopQueue ........................... 循环队列_基于动态数组实现 3.4.PriorityQueue ....................... 优先队列_基于最大二叉堆实现 3.5.ArrayPriorityQueue ............. 优先队列_基于动态数组实现 3.6.LinkedListPriorityQueue ..... 优先队列_基于链表实现 3.7.ArrayStack ............................. 栈_基于动态数组实现 3.8.LinkedListStack ..................... 栈_基于链表实现 3.9.BSTSet ..................................... 集合_基于二分搜索树实现 3.10.LinkedListSet ....................... 集合_基于链表实现 3.11.BSTMap ................................ 映射_基于二分搜索树实现 3.12.AVLTreeMap ....................... 映射_ 基于AVL树实现 3.13.LinkedListMap .................... 映射_基于链表实现 3.14.MaxHeap ............................. 最大二叉堆 3.15.SegmentTree ...................... 线段树 3.16.Trie ......................................... 字典树 3.17.QuickFind ............................ 并查集_基于数组实现 3.18.QuickUnion ......................... 并查集_基于树思想实现
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值