单链表的基本操作和实现

(一)链表的定义和介绍在这里就不多介绍了,我们现在看文章大概最烦的就是一上来就长篇大论,这样直接劝退了很多人,所以我们选择从简。

    头节点: 是单链表的头,是一个特殊的节点,只有指针域,没有数据域。

    

    节点:由两部分构成,第一部分是数据域,存储的是该节点的内容,第二部分是指针域,用来存储下一节点的地址,通过改地址可以访问下一个节点。

      

    单链表:由头节点和若干节点组成的链表。

    

    这是一个有三个节点的链表。

(二)链表的存储结构

  (1)从上篇文章我们得知顺序表在计算机中存储的位置是连续的,就像宿舍楼一层的房间,都是相邻的,但链表不一样,链表不一定是相邻的,只不过每一个节点都会存储下一个节点的地址。比如说,小明有小红的地址,小红呢有小白的地址,小白又有小兰的地址,小黑有小明的地址,他们五个是一个班的,所以他们之间就形成了一个关系:

  

这就是一个单链表的结构,单是指只有前驱节点有后继节点的地址,只有单向性,当然也有双向链表,后面遇到了我们再讲。

(2)上面是我们自己抽象化的一个链表,在计算机中的存储结构是下图:    

(三)代码分析

(1)头文件

#include <iostream>     //基本的输入输出
using namespace std;   //声明命名空间
#include<string.h>     //字符串头文件

(2)预处理

#define OK 1
#define ERROR 0
#define ElemType int

(3)创建节点,很明显,这里要用到结构体,结构体有两个成员,一个是数据域,一个是指针域。

typedef struct LNode
{
    struct LNode *next;   //节点指针域
    string data;          //节点数据域
}*LinkList,LNode;          //一个是结构体指针,一个是结构体名称

(4)初始化函数,创建一个头节点,并将指针域置空。

ElemType Init_Linklist(LinkList &L)  //初始化链表
{
    L = new LNode;                  //创建一个头节点
    L->next = NULL;                 //并将头节点的指针域置空
    return OK;
}

(5)输出链表函数,这个函数呢,是将单链表中的每个元素按顺序输出,为了美观,用"->"隔开。

   实现步骤:①定义一个节点指针,指向第一个节点。(注意:这里的第一个节点是头节点之后的第一个。)

        ②while判断当前节点是否为空,如果不是空,就输出该节点内容,然后指针下移,下面的if是最后一个节点不输出"->",这样链表看起来会更美观。

void Printf_LNode(LinkList L)   //遍历输出链表内容
{
    cout<<"链表内容:";
    LNode *p = L->next;            //节点指针,用来遍历节点
    while(p)
    {
        cout<<p->data;             //输出节点的数据域
        p = p->next;
        if(p)                     //判断下一个节点是否为空,如果是就不输出"->"
        {
            cout<<"->";
        }
    }
    cout<<endl;

}

(6)前插法建立链表,即每次在第一个节点的位置插入新节点。咱们先自己想一下这个过程,首先,我们要新建一个节点吧,然后将新节点的指针域指向下一个节点,再把上一个节点的指针域指向自己,不就插入成功了嘛。看一下图片:

    

   实现步骤:①输出提醒,并得到你要创建节点的个数n。

        ②创建一个节点指针,便于后面改指针的操作。

        ③初始化头节点,并将指针域置空。

        ④要创建n个节点,自然需要循环n次,首先开辟一个新节点,并让p指向新节点,将新节点的指针指向下一个节点,再让上一个节点指向自己,然后在输入该节点的内容。

        ⑤输出链表。

void Create_LinkList_h(LinkList &L)    //前插法创建单链表
{
    int n;
    cout<<"请输入你要创建的链表的结点个数:";
    cin>>n;
    LNode *p;
    L = new LNode;        //初始化头节点
    L->next = NULL;
    cout<<"将要采用头插法创建单链表,请逆序输入"<<n<<"个节点内容:";
    for(int i = 0;i < n;i++)    //循环创建节点
    {
        p = new LNode;
        p->next = L->next;
        L->next = p;
        cin>>p->data;
    }
    Printf_LNode(L);          //输出链表内容
}

(7)后插法建立链表,尾插法是将每个元素插在最后,故称为后插法。与前插法大致相同,不过比前插法多一个节点指针,首先新建一个节点,然后自己的指针域为空,因为自己已经是最后一个节点了,然后让上一个节点指向自己,然后插入成功。

    实现步骤:①首先得到你要输入的节点个数n

         ②初始化头节点

         ③定义一个节点指针r,并指向L

         ④循环n次,每一次都先申请一个节点,输入节点内容,因为是后插,所以每一个节点的指针域都指向空,然后再让上一个节点指向自己,然后r后移一个节点。

void Create_LinkList_r(LinkList &L)   //尾插法创建链表
{
    int n;                              //节点个数
    cout<<"请输入你要创建的链表的结点个数:";
    cin>>n;
    L = new LNode;                    //初始化头节点
    L->next = NULL;
    LNode *r = L;
    cout<<"将要采用尾插法创建单链表,请输入"<<n<<"个节点内容:";
    for(int i = 0;i < n;i++)    //循环创建节点
    {
        LNode *p;
        p = new LNode;
        cin>>p->data;
        p->next = NULL;
        r->next = p;
        r = p;
    }
    Printf_LNode(L);
}

(8)取值,通过输入的位置, 遍历链表,找到对应位置的值,然后赋值即可。

    实现步骤:①首先输入你要取得元素的位置

         ②定义一个新的节点指针,并指向头节点的下一个节点,同时呢也定义一个j,用来记录节点的值。

         ③用while循环,让指针连续下移到相应位置,同时判断j的值是否达到n。

         ④如果已经移到空节点了,或者说j已经大于n了,就表明越界了。

         ⑤如果没有越界,就赋值即可。

ElemType Get_ElemType(LinkList L,string &name)
{
    int n;                      //元素位置
    cout<<"请输入你要取得元素的位置:";
    cin>>n;
    LNode *p = L->next;    //新建节点指针,并指向下一个节点
    int j = 1;
    while(p&&j<n)         //指针移到要取得位置
    {
        p = p->next;
        j++;
    }
    if(!p || j>n)           //判断是否越界
    {
        return ERROR;
    }
    name = p->data;        //取值
}

(9)插入节点,其实也就是先开辟一个节点,然后让新节点指向下一个节点,再让新节点的上一个节点指向自己,就插入成功了。

    实现步骤:①首先得到你要插入的位置

         ②定义一个节点指针,指向第一个节点

         ③指针连续下移,移到n-1的节点。

         ④判断是否越界

         ⑤新建一个节点和一个指向新节点的指针s,然后输入插入的节点的值。

            ⑥改指针,将上一个节点的指针赋给新节点,然后让上一个节点指向自己,就插入成功了。

 

ElemType Insert_LNode(LinkList &L)   //插入节点函数
{
    int n;      //插入得位置
    cout<<"请输入你要插入的位置:";
    cin>>n;
    LNode *p = L->next;
    int j = 1;
    while(p&&j<(n - 1))   //新建节点指针,并指向下一个节点
    {
        p = p->next;
        j++;
    }
    if(!p || j > (n - 1))  //判断是否越界
    {
        return ERROR;
    }
    LNode *s;         //指向新建节点的指针
    s = new LNode;
    cout<<"请输入你要插入的元素:";
    cin>>s->data;    //输入新节点的数据域内容
    s->next = p->next;
    p->next = s;
    Printf_LNode(L);   //输出单链表

}

 (10)删元素,其实跟之前插元素的实现差不多,都是先移到你要删除的位置,然后把你想要删除节点的指针域赋给前一个节点,然后释放节点即可。

    实现步骤:①输出一边当前链表的内容,然后挑选并得到你要删除的位置,同时定义一个节点指针q。

         ②定义一个节点指针指向第一个节点,并定义一个计数变量j。

         ③指针p连续下移到要删除的元素的前一个位置,然后在把该节点的指针域(q = p->next),赋给节点指针q,这样q就指向了我们要删除的节点,然后让q指向我们要删除的节点的下一个节                                        点,然后释放q节点即可。

ElemType Del_LNode(LinkList &L)  //删除节点函数
{
    int n;
    LNode *q;       //首先定义一个节点指针
    Printf_LNode(L);
    cout<<"请输入你要删除的位置:";
    cin>>n;
    LNode *p = L->next;
    int j = 1;
    while(p&&(j< n - 1))  //指针下移
    {
        p = p->next;
        ++j;
    }
    if(!p || j > (n - 1))  //判断是否越界
    {
        return ERROR;
    }
    q = p->next;
    p->next = q->next;
    delete q;
    Printf_LNode(L);
}

(11)菜单函数,只是一些普通的输出,就不用解释了吧

void Menu()   //菜单函数
{
    cout<<"<<<<<<<<<<<<<<<菜单>>>>>>>>>>>>>>>"<<endl;
    cout<<"1.初始化单链表"<<endl;
    cout<<"2.头插法创建一个单链表"<<endl;
    cout<<"3.尾插法创建一个单链表"<<endl;
    cout<<"4.取单链表中的某个元素"<<endl;
    cout<<"5.在单链表某个位置插入元素"<<endl;
    cout<<"6.在单链表删除某个位置的元素"<<endl;
    cout<<"7.输出链表内容"<<endl;
    cout<<"8.菜单"<<endl;
    cout<<"9.退出系统!"<<endl;
    cout<<"输入对应选项,执行对应操作"<<endl;
}

(12)主函数:首先创建一个链表指针,输出菜单,然后进入while循环,通过switch输入不同的选项进入不同的case,然后执行不同的函数,这里为了能够退出系统(跳出循环)使用了goto语句来结束循环。这里需要特别说明的可能就是case 4和case 5,定义了两个标志变量,用来记录程序退出的值,看是否出错

int main()
{
    LinkList La;  //链表指针
    int select;
    Menu();
    while(1)   //循环输入选项
    {
        cout<<"请输入选项:";
        cin>>select;
        switch(select)   //判断选项,并执行对应的函数
        {
        case 1:
            {
                Init_Linklist(La);
                cout<<"初始化后单链表为空,要想进行操作,请先创建一个单链表"<<endl;
                Create_LinkList_h(La);
                break;
            }
        case 2:
            {
                Create_LinkList_h(La);
                break;
            }
        case 3:
            {
                Create_LinkList_r(La);
                break;
            }
        case 4:
            {
                int flag;         //标志变量,用来记录退出码,用来判断是否为正常退出
                string name;
               flag = Get_ElemType(La,name);
               if(!flag)
               {
                   cout<<"取出失败,越界!"<<endl;
               }
                cout<<name<<endl;
                break;
            }
        case 5:
            {
                int flag;
                flag = Insert_LNode(La);    //标志变量,用来记录退出码,用来判断是否为正常退出
                if(!flag)
                {
                    cout<<"插入失败,越界!"<<endl;
                }
                break;
            }
        case 6:
            {
                Del_LNode(La);
                break;
            }
        case 7:
            {
                Printf_LNode(La);
                break;
            }
        case 8:
            {
                Menu();
                break;
            }
        case 9:
            {
                cout<<"多谢使用"<<endl;
                goto unloop;      //使用goto语句,跳转,使程序结束
            }
        default:
        {
            cout<<"输入有误!"<<endl;
            break;
        }
        }
    }
    unloop:return 0;      //跳转到程序结束语句
}

(四)完整代码

#include <iostream>     //基本的输入输出
using namespace std;   //声明命名空间
#include<string.h>     //字符串头文件

#define OK 1
#define ERROR 0
#define ElemType int


typedef struct LNode
{
    struct LNode *next;   //节点指针域
    string data;          //节点数据域
}*LinkList,LNode;          //一个是结构体指针,一个是结构体名称

ElemType Init_Linklist(LinkList &L)  //初始化链表
{
    L = new LNode;                  //创建一个头节点
    L->next = NULL;                 //并将头节点的指针域置空
    return OK;
}

void Printf_LNode(LinkList L)   //遍历输出链表内容
{
    cout<<"链表内容:";
    LNode *p = L->next;            //节点指针,用来遍历节点
    while(p)
    {
        cout<<p->data;             //输出节点的数据域
        p = p->next;
        if(p)                     //判断下一个节点是否为空,如果是就不输出"->"
        {
            cout<<"->";
        }
    }
    cout<<endl;

}

void Create_LinkList_h(LinkList &L)    //前插法创建单链表
{
    int n;
    cout<<"请输入你要创建的链表的结点个数:";
    cin>>n;
    LNode *p;
    L = new LNode;        //初始化头节点
    L->next = NULL;
    cout<<"将要采用头插法创建单链表,请逆序输入"<<n<<"个节点内容:";
    for(int i = 0;i < n;i++)    //循环创建节点
    {
        p = new LNode;
        p->next = L->next;
        L->next = p;
        cin>>p->data;
    }
    Printf_LNode(L);          //输出链表内容
}

void Create_LinkList_r(LinkList &L)   //尾插法创建链表
{
    int n;                              //节点个数
    cout<<"请输入你要创建的链表的结点个数:";
    cin>>n;
    L = new LNode;                    //初始化头节点
    L->next = NULL;
    LNode *r = L;
    cout<<"将要采用尾插法创建单链表,请输入"<<n<<"个节点内容:";
    for(int i = 0;i < n;i++)    //循环创建节点
    {
        LNode *p;
        p = new LNode;
        cin>>p->data;
        p->next = NULL;
        r->next = p;
        r = p;
    }
    Printf_LNode(L);
}

ElemType Get_ElemType(LinkList L,string &name)
{
    int n;                      //元素位置
    cout<<"请输入你要取得元素的位置:";
    cin>>n;
    LNode *p = L->next;    //新建节点指针,并指向下一个节点
    int j = 1;
    while(p&&j<n)         //指针移到要取得位置
    {
        p = p->next;
        j++;
    }
    if(!p || j>n)           //判断是否越界
    {
        return ERROR;
    }
    name = p->data;        //取值
}

ElemType Insert_LNode(LinkList &L)   //插入节点函数
{
    int n;      //插入得位置
    cout<<"请输入你要插入的位置:";
    cin>>n;
    LNode *p = L->next;
    int j = 1;
    while(p&&j<(n - 1))   //新建节点指针,并指向下一个节点
    {
        p = p->next;
        j++;
    }
    if(!p || j > (n - 1))  //判断是否越界
    {
        return ERROR;
    }
    LNode *s;         //指向新建节点的指针
    s = new LNode;
    cout<<"请输入你要插入的元素:";
    cin>>s->data;    //输入新节点的数据域内容
    s->next = p->next;
    p->next = s;
    Printf_LNode(L);   //输出单链表

}

ElemType Del_LNode(LinkList &L)  //删除节点函数
{
    int n;
    LNode *q;       //首先定义一个节点指针
    Printf_LNode(L);
    cout<<"请输入你要删除的位置:";
    cin>>n;
    LNode *p = L->next;
    int j = 1;
    while(p&&(j< n - 1))  //指针下移
    {
        p = p->next;
        ++j;
    }
    if(!p || j > (n - 1))  //判断是否越界
    {
        return ERROR;
    }
    q = p->next;
    p->next = q->next;
    delete q;
    Printf_LNode(L);
}
void Menu()   //菜单函数
{
    cout<<"<<<<<<<<<<<<<<<菜单>>>>>>>>>>>>>>>"<<endl;
    cout<<"1.初始化单链表"<<endl;
    cout<<"2.头插法创建一个单链表"<<endl;
    cout<<"3.尾插法创建一个单链表"<<endl;
    cout<<"4.取单链表中的某个元素"<<endl;
    cout<<"5.在单链表某个位置插入元素"<<endl;
    cout<<"6.在单链表删除某个位置的元素"<<endl;
    cout<<"7.输出链表内容"<<endl;
    cout<<"8.菜单"<<endl;
    cout<<"9.退出系统!"<<endl;
    cout<<"输入对应选项,执行对应操作"<<endl;
}

int main()
{
    LinkList La;  //链表指针
    int select;
    Menu();
    while(1)   //循环输入选项
    {
        cout<<"请输入选项:";
        cin>>select;
        switch(select)   //判断选项,并执行对应的函数
        {
        case 1:
            {
                Init_Linklist(La);
                cout<<"初始化后单链表为空,要想进行操作,请先创建一个单链表"<<endl;
                Create_LinkList_h(La);
                break;
            }
        case 2:
            {
                Create_LinkList_h(La);
                break;
            }
        case 3:
            {
                Create_LinkList_r(La);
                break;
            }
        case 4:
            {
                int flag;         //标志变量,用来记录退出码,用来判断是否为正常退出
                string name;
               flag = Get_ElemType(La,name);
               if(!flag)
               {
                   cout<<"取出失败,越界!"<<endl;
               }
                cout<<name<<endl;
                break;
            }
        case 5:
            {
                int flag;
                flag = Insert_LNode(La);    //标志变量,用来记录退出码,用来判断是否为正常退出
                if(!flag)
                {
                    cout<<"插入失败,越界!"<<endl;
                }
                break;
            }
        case 6:
            {
                Del_LNode(La);
                break;
            }
        case 7:
            {
                Printf_LNode(La);
                break;
            }
        case 8:
            {
                Menu();
                break;
            }
        case 9:
            {
                cout<<"多谢使用"<<endl;
                goto unloop;      //使用goto语句,跳转,使程序结束
            }
        default:
        {
            cout<<"输入有误!"<<endl;
            break;
        }
        }
    }
    unloop:return 0;      //跳转到程序结束语句
}

 

(五)运行结果

 程序在健壮性方面还有很多不足,各位大神如果有什么好的想法或者发现我的某些错误,欢迎指正!最后希望大家给我一个支持,谢谢!

    

posted @ 2020-10-13 17:39  stronger_zjf  阅读(989)  评论(0编辑  收藏  举报