(一)链表的定义和介绍在这里就不多介绍了,我们现在看文章大概最烦的就是一上来就长篇大论,这样直接劝退了很多人,所以我们选择从简。
头节点: 是单链表的头,是一个特殊的节点,只有指针域,没有数据域。
节点:由两部分构成,第一部分是数据域,存储的是该节点的内容,第二部分是指针域,用来存储下一节点的地址,通过改地址可以访问下一个节点。
单链表:由头节点和若干节点组成的链表。
这是一个有三个节点的链表。
(二)链表的存储结构
(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; //跳转到程序结束语句 }
(五)运行结果
程序在健壮性方面还有很多不足,各位大神如果有什么好的想法或者发现我的某些错误,欢迎指正!最后希望大家给我一个支持,谢谢!