这块涉及很多指针,所以对很多同学来说都会有难度。我觉的应该先抓一些基本操作,然后再去看大的算法设计是由哪些基本操作构成的。基本操作大概包括:
书上的结构体、动态内存分配都是用C语言的结构体实现的,但我觉得大家可能对C++的new
和delete
更熟悉一点,而且C++的结构体形式也更加简单,所以后续的代码都将用C++给出
单链表是由一个个的结点组成,每个节点都是指针类型的变量,都包含数据域和指针域。其中数据域用来存储数据,指针域用来连接下一个结点:
我们用来存储数据的单链表是由一个个上面这样的结点组成的,对于每个节点,你可以通过它找到它下一个结点,但你不可以通过它找到它上一个结点,这就决定了要对单链表进行操作,必须找到目标结点的上一个结点
为了便于操作,我们经常给单链表添加头指针和尾指针,分别指向首结点和尾结点
struct node{
int data; //数据域
node *next; //指针域
};
与创建数组不同,最初创建单链表,其实就是创建头指针,后续的“扩大单链表”,其实就是给头指针上挂上其他的链条。
所以我建议大家直接将“创建单链表”理解为“创建头指针”,然后将其他的赋值、完善等操作理解为“给头指针挂上新的链条”。
创建头指针用到如下代码
node *head;
head=new node();
head->next=NULL;
方便起见,我们把后两句存放在一个初始化函数里:
void initList(node *&head){
//初始化函数
head=new node();
head->next=NULL;
}
这样以后创建头指针的时候,只要写:
node *head;
initList(head);
就可以创建好了。
插入元素可以很形象地理解为在一条链子上插入新的一环。比如将结点p
插入到两个结点之间,也就是插入到结点before
之后,其实是这样的过程:
p->next=before->next; //p的指针域连接last后面的环
before->next=p; //last的指针域连接p
如果我要在最头上插入一个新结点,那我找不到before
,怎么办?
这就体现出头指针head
的作用了,头指针head
的存在保证了就算你想在最开始的位置插入结点,你还是能找到一个before
,来完成你的操作。
p->next=head->next; //p的指针域指向原来的首结点
head->next=p; //头指针指向p,p成为新的首结点
在尾部插入元素时,最后的元素相当于before
,但没有before->next
。我们在最后加一个尾指针tail
,这样插入元素时,情况又变成了在两个“结点”之间插入一个新的结点。
但值得注意的是,尾指针指向最后一个元素,而不是最后一个元素指向尾指针,所以插入操作与前面略有不同:
tail->next=p; //tail->next表示尾结点的指针域
tail=p; //尾指针指向新的尾结点p
需要注意的是,头指针其实是头结点的指针域,也就是说,头指针本身是一个结点,但尾指针并不是结点,它只是一个单纯的指针。
头指针通过指针域来发挥“指针”的作用,也就是说,首结点是head->next
。在实际应用中,我们要输出首结点的数据,其实是这样输出的:
cout<<head->next->data;
而尾指针就是一个纯粹的指针,尾结点就是tail
,所以如果要输出尾结点的数据,是这样的:
cout<<tail->data;
在初始化单链表,也就是创建头指针的过程中,如果需要添加尾指针,应该是这样的:
void initList_tail(node *&head){
head=new node();
head->next=NULL;
node *tail=head;
}
因为刚创建头指针的过程中,头指针本身就是最后一个元素,所以按照尾指针的定义,应该让尾指针指向最后一个元素,也就是指向头指针。
删除元素也可以类比取下链条中的某一环,并把这一环销毁。思路应该是创建一个临时指针,用来代表这个即将被销毁的结点,然后重新连接链条,并销毁待删除的结点。
node *q=before->next;
before->next=q->next;
delete q;
单链表的遍历特别有顺藤摸瓜捣毁黑帮的感觉,就像你手里掌握着黑帮老大——头指针,然后通过他,你能找到他的小弟,然后找到小弟的小弟…最后找到的那个人,他是黑帮最底层的人,他没有小弟——NULL
。
需要注意的有两点:
p
,用它来联系小弟们,以保证老大head
不被改变。综上,遍历的操作应该是:
void findBro(node *head){
node *p=head; //工作指针
//或:node *p=head->next;
while(p!=NULL){
//判断当前的人是不是最底层小弟
//操作
p=p->next; //找他的小弟
}
}
掌握了遍历的方法后,我们可以在遍历过程中夹带一点私货,比如记录一下这个黑帮一共有多少人——求单链表长度:
int getLength(node *head){
//求长度
int count=0;
node *p=head->next; //工作指针
while(p!=NULL){
p=p->next;
count++;
}
return count;
}
有了count
的引入,就为我们的操作带来了更多的可能,比如判断if(count==i)
,以实现找到第i个元素等,在后面的部分会给出相应的代码
void output(node *head){
//其实就是在遍历过程中加一个输出
node *p=head->next;
while(p!=NULL){
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
拆分为基本操作:遍历+单个元素删除
void destoryList(node *&head){
//销毁
node *pre=head,*p=head->next; //从头结点开始删除
while(p!=NULL){
delete pre;
pre=p;
p=p->next;
}
}
拆分为基本操作:不断地往表头插入新结点
void createList(node *&head,int a[],int len){
//已有数组a,数组长度len
for(int i=0;i<len;i++){
node *s=new node(); //创建一个游离的新结点
s->data=a[i]; //给新结点赋值
s->next=head->next; //把新结点插入链表的前端
head->next=s;
}
}
拆分为基本操作:不断地往表尾插入新的结点
void createList_tail(node *&head,int a[],int len){
node *tail=head;
for(int i=0;i<len;i++){
node *s=new node(); //创建一个游离的新结点
s->data=a[i]; //给新结点赋值
tail->next=s; //把新结点插入链表的尾端
tail=s;
}
}
拆分为基本操作:遍历到第i-1个元素的位置,删除它后面的元素
void deleElem(node *&head,int i){
//删除第i个元素
node *p=head; //工作指针
int count=1;
while(p!=NULL&&count!=i){
p=p->next;
count++;
}
node *q=p->next; //基本操作——删除
p->next=q->next;
delete q;
}
拆分为基本操作:遍历到第i个元素的位置,在它后面插入新的元素
void insElem(node *&head,int value,int i){
//在第i个元素后插入值为value的结点
node *p=head->next; //工作指针
int count=1;
while(p!=NULL&&count!=i){
p=p->next;
count++;
}
node *q=new node(); //创建
q->data=value;
q->next=p->next;
p->next=q;
}
ps. i=0
时在头结点后直接插入结点
拆分为基本操作:遍历单链表直到找到value,然后输出此时的count
int findValue(node *head,int value){
node *p=head->next;
int count=1;
while(p!=NULL&&p->data!=value)
{
p=p->next;
count++;
}
return count;
}
题目链接:传送门
设计整数单链表的基本运算程序,并用相关数据进行测试
顺序输入单链表A的各个元素
第一行:创建单链表A后,输出所有元素
第二行:删除第一个元素,输出删除后的所有元素
第三行:输出删除元素后表的长度
第四行:在第二元素处插入一个新的元素100
第五行:输出第一个元素100所在位置
1 2 3 4 0 9
1 2 3 4 0 9
2 3 4 0 9
5
2 100 3 4 0 9
2
#include
using namespace std;
struct node{
//定义链表
int data;
node *next;
};
//头结点用head表示
void initList(node *&head){
//初始化函数
head=new node();
head->next=NULL;
}
void destoryList(node *&head){
//销毁链表
node *pre=head,*p=head->next; //从头结点开始删除
while(p!=NULL){
delete pre;
pre=p;
p=p->next;
}
}
int getLength(node *head){
//求长度
int count=0;
node *p=head->next;
while(p!=NULL){
p=p->next;
count++;
}
return count;
}
int findValue(node *head,int value){
//找到值为value的结点
node *p=head->next;
int count=1;
while(p!=NULL&&p->data!=value)
{
p=p->next;
count++;
}
return count;
}
void insElem(node *&head,int value,int i){
//在第i个元素后插入值为value的结点
node *p=head->next;
int count=1;
while(p!=NULL&&count!=i){
p=p->next;
count++;
}
node *q=new node();
q->data=value;
q->next=p->next;
p->next=q;
}
void deleElem(node *&head,int i){
//删除第i个元素
node *p=head;
int count=1;
while(p!=NULL&&count!=i){
p=p->next;
count++;
}
node *q=p->next;
p->next=q->next;
delete q;
}
void output(node *head){
//输出
node *p=head->next;
while(p!=NULL){
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
void createList_tail(node *head,int a[],int len){
//尾插法创建链表
node *tc=head;
for(int i=0;i<len;i++){
node *s=new node();
s->data=a[i];
tc->next=s;
tc=s;
}
}
int main()
{
node *head;
initList(head);
int a[10000];
for(int i=0;i<6;i++)cin>>a[i];
createList_tail(head,a,6);
output(head);
deleElem(head,1);
output(head);
cout<<getLength(head)<<endl;
insElem(head,100,1);
output(head);
cout<<findValue(head,100)<<endl;
destoryList(head);
return 0;
}
题目链接:传送门
设计有序整数单链表的插入运算程序,并用相关数据进行测试
按升序顺序输入单链表A的各个元素和待插入元素
第一行:创建单链表A后,输出所有元素
第二行:输出按照升序插入后的所有元素
0 1 2 3 4 9
7
0 1 2 3 4 9
0 1 2 3 4 7 9
这个题需要稍微拐一个弯,就是需要找到插入位之前的元素,所以在比较大小的过程中,需要把手伸的长一点,即如果下一个元素的数值大于待插入元素,那么工作指针p停留在当前元素:
#include
using namespace std;
struct node{
int data;
node *next;
};
//头结点用head表示
void initList(node *&head){
//无值初始化
head=new node();
head->next=NULL;
}
void destoryList(node *&head){
//销毁
node *pre=head,*p=head->next; //从头结点开始删除
while(p!=NULL){
delete pre;
pre=p;
p=p->next;
}
}
void output(node *head){
node *p=head->next;
while(p!=NULL){
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
void createList_tail(node *head,int a[],int len){
node *tc=head;
for(int i=0;i<len;i++){
node *s=new node();
s->data=a[i];
tc->next=s;
tc=s;
}
}
int main()
{
node *head;
initList(head);
int a[1000],value;
for(int i=0;i<6;i++)cin>>a[i];
cin>>value;
createList_tail(head,a,6);
output(head);
node *p=head->next;
while(p!=NULL&&p->next->data<=value)
p=p->next;
node *s=new node();
s->data=value;
s->next=p->next;
p->next=s;
output(head);
destoryList(head);
return 0;
}
题目链接:传送门
使用尾插法创建链表,查找链表值最大结点(假定当前链表最大值唯一),在最大值结点后插入一个比最大值大10的结点。
#include
#include
using namespace std;
typedef int ElemType;
#define MAX_SIZE 100
typedef struct node
{
ElemType data; //数据域
struct node *next; //指针域
} SLinkNode; //单链表结点类型
void CreateListR(SLinkNode *&L, ElemType a[], int n) //尾插法建表
{
SLinkNode *s, *tc; int i;
L = (SLinkNode *)malloc(sizeof(SLinkNode)); //创建头结点
tc = L; //tc为L的尾结点指针
for (i = 0; i < n; i++)
{
s = (SLinkNode *)malloc(sizeof(SLinkNode));
s->data = a[i]; //创建存放a[i]元素的新结点s
tc->next = s; //将s结点插入tc结点之后
tc = s;
}
tc->next = NULL; //尾结点next域置为NULL
}
int InsElemSpe(SLinkNode *&L) //插入结点
{
// 在此处补充你的代码
return 1; //插入运算成功,返回1
}
void DispList(SLinkNode *L) //输出单链表
{
SLinkNode *p = L->next;
while (p != NULL)
{
cout<<p->data<<" ";
p = p->next;
}
cout<<endl;
}
void DestroyList(SLinkNode *&L) //销毁单链表L
{
SLinkNode *pre = L, *p = pre->next;
while (p != NULL)
{
free(pre);
pre = p; p = p->next; //pre、p同步后移
}
free(pre);
}
int main()
{
ElemType a[MAX_SIZE];
SLinkNode *L;
int nlength;
cin >> nlength;
for (int i = 0; i < nlength; i++)
cin >> a[i];
CreateListR(L, a, nlength);
InsElemSpe(L);
DispList(L);
DestroyList(L);
return 0;
}
输入分两行数据,第一行是尾插法需要插入的数据的个数,第二行是具体插入的数值。
按程序要求输出
4
40 50 70 65
40 50 70 80 65
只给出自己写的那部分函数:
int InsElemSpe(SLinkNode *&L)
{
// 在此处补充你的代码
SLinkNode *p=L->next;
int max_value=0;
while (p!=NULL){
max_value=max(max_value,p->data);
p=p->next;
}
SLinkNode *pp=L->next;
while(pp!=NULL&&pp->data!=max_value)
pp=pp->next;
SLinkNode *q=(SLinkNode*)malloc(sizeof(SLinkNode));
q->data=max_value+10;
q->next=pp->next;
pp->next=q;
return 1; //插入运算成功,返回1
}