数据结构与算法——单链表及第三次实验题解

数据结构与算法——单链表及第三次实验题解

学习思路

这块涉及很多指针,所以对很多同学来说都会有难度。我觉的应该先抓一些基本操作,然后再去看大的算法设计是由哪些基本操作构成的。基本操作大概包括:

  • 创建单链表;
  • 插入元素;
  • 删除元素;
  • 遍历单链表;

书上的结构体、动态内存分配都是用C语言的结构体实现的,但我觉得大家可能对C++的newdelete更熟悉一点,而且C++的结构体形式也更加简单,所以后续的代码都将用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个元素

拆分为基本操作:遍历到第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个元素后插入值为value的结点

拆分为基本操作:遍历到第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的元素,并返回它的位置

拆分为基本操作:遍历单链表直到找到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;
}

题解

1:整数单链表的基本运算-1

题目链接:传送门

描述

设计整数单链表的基本运算程序,并用相关数据进行测试

输入

顺序输入单链表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

题解1

#include <iostream>
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;
}

2:整数单链表的基本运算-2

题目链接:传送门

描述

设计有序整数单链表的插入运算程序,并用相关数据进行测试

输入

按升序顺序输入单链表A的各个元素和待插入元素

输出

第一行:创建单链表A后,输出所有元素
第二行:输出按照升序插入后的所有元素

样例输入
0 1 2 3 4 9
7
样例输出
0 1 2 3 4 9
0 1 2 3 4 7 9

题解2

这个题需要稍微拐一个弯,就是需要找到插入位之前的元素,所以在比较大小的过程中,需要把手伸的长一点,即如果下一个元素的数值大于待插入元素,那么工作指针p停留在当前元素

#include <iostream>
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;
}

3:单链表的插入和显示操作

题目链接:传送门

描述

使用尾插法创建链表,查找链表值最大结点(假定当前链表最大值唯一),在最大值结点后插入一个比最大值大10的结点。

#include <malloc.h>
#include <iostream>
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

题解3

只给出自己写的那部分函数:

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

}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值