数据结构复习(打卡~线性表(2)单链表)

目录

一、链表有关概念

二、链表的基本操作:

三、拓展 循环和双向链表:

四、项目实践(以带头结点的单链表为基础):


一、链表有关概念

  • 结点的组成:

示意图:

数据域:用来存储数据元素的值

指针域:存放的是该结点的直接后继结点地址,以便将各个独立的结点连接起来。

  • 单链表

链表:用链式存储结构表示的线性表称为链表。

单链表:每个结点只有一个指针域的链表称为单链表。

  • 开始结点、头结点、尾结点、头指针

1、开始结点:链表中存储第一个数据元素(a1)的结点。

2、尾结点:  链表中存储最后一个数据元素(an)的结点

                      尾结点没有后继结点,所以其指针域为的值NULL

3、头结点:再开始结点前附加的一个结点。

4、头指针:指向链表中第一个结点(头结点或是无头结点的开始结点)的指针。

  • (链表)结点的构造
typedef char DataType;//定义结点的数据类型

typedef struct node{  //结点类型名称定义

DataType data;    //结点数据域

struct node *next;//结点的指针域 (之所以是结构体类型指针,是因为指针域里存放的是整个结构体的地址)

}ListNode;            //结构体类型标识符

 typedef ListNode* LinkList;//表示结构体的地址域 (其实可有可无,可以用 ListNode **x表示,这里知识为了方便区分)

 ListNode *p;         //指向结点的指针

 LinkList head;       //指向链表的头指针

注意事项:

  1. 这里的"ListNode *"等价于"LinkList",只是说用途不同,所以同一个指针类型,用不同名字以示区分。
  2. LinkList类型的指针变量 head 表示它是单链表的头指针。
  3. ListNode *类型的指针变量 p 表示它是指向某一结点的指针。

  • 结点的生成和释放
  1. 结点的生成:

建立新结点时,需要向系统申请一个指定大小和类型的存储空间,来生成一个新结点,新结点必须要用指向结点的指针来指向。

例如:p = ( ListNode * )malloc( sizeof ( ListNode )

指向结点的指针)= (指定类型)+ (指定大小

      2.结点的释放:

当用户申请的某个存储空间(比如 p 指向的空间)不需要时,可使用free(p);释放 p 指向的结点空间,以便其他应用使用,不至于造成空间的浪费。

  • 单链表示意图:
  1. 不带 头结点 的单链表:

     2.带 头结点 的单链表:

    3.带头结点和不带头结点的区别:

不带头节点:

此时头指针指向第一个节点

h->a1->a2->a3->…… // 头指针存放的是第一个节点的地址,即h,也就是说(*h)表示的是第一个节点

带头结点:

此时头指针指向头结点

h->headnode->a1->a2->a3->……  // 头指针存放的是头结点的地址,也就是说(*h)表示的是头结点

总的来说,就是带头结点时不管是否为空表,头指针的值都不会变化,都指向头结点。而不带头结点则需要根据不同情况修改头指针的值

不带头节点写代码不方便,需要专门写一段代码来解决第一个节点。

所以操作不统一,有所不便,所以绝大数时候使用带头结点的方式较为方便。

二、链表的基本操作:

 // ListNode *p,*newNode, LinkList head实际上都是该对象的地址)

(一)链表的建立

1、头插法

算法分析:

头插法建立单链表,是从一个空表开始,重复读入数据,生成新结点,将读入的数据存放在新结点的数据域中,然后将新结点插入当前链表的表头上,直到读入具体的结束标志为止。

 算法核心:

>向系统申请新结点的存储空间: 

newNode = ( ListNode *)malloc( sizeof ( ListNode ) );

>将读入的数据存储到新结点的数据域中:

 newNode ->data = ch;

>将头指针 head (head指针一开始为空NULL,是为了第一个结点作为尾结点没有直接后继结点的特征)存放到新结点的指针域中:

 newNode ->next = head;(head->next)

>头指针指针域存放新结点,相当于新结点连在了头指针后面:

 head = newNode

之后的结点(重复以上操作,不过从第二个结点开始就如图下示例)

算法示例图:

 

  算法实现:

 //头插法建立单链表
 LinkList CreatListF(void) //因为返回的是单链表,所以函数类型也是单链表 
 {
 	DataType newdata;       //新结点存储的数据
	LinkList head;          //头指针(可以视为整个单链表)
	ListNode *newNode;       //工作指针(新结点)双重指针 
	head = NULL;            //初始链表内容为空
	printf("请输入链表各结点的数据(字符型):\n");
	while((newdata = getchar()) != '\n')
	{
		newNode = (ListNode *)malloc(sizeof(ListNode));//向系统申请新结点的存储空间
	    newNode->data = newdata;//将读入的数据存储到新结点的数据域中
		newNode->next = head;   //将头指针 head 地址(包含数据域和指针域)存放到新结点的指针域中
		head->next = newNode;   //头指针指针域存放新结点地址
	}
	return head;(返回整个单链表)
 }

2、尾插法:

算法分析:

需要借助一个辅助指针rear指向当前链表最后一个节点,每次处理辅助指针指向的节点和新增的节点的关系即可。

算法核心:

>建立一个头结点,将头指针和辅助指针,都指向头结点:

 rear = head (可以按照 rear->next = head ->next 来理解)

>向系统申请新结点的存储空间: 

head = ( ListNode *)malloc( sizeof ( ListNode ) );

newNode = ( ListNode *)malloc( sizeof ( ListNode ) );

>将读入的数据存储到新结点的数据域中:

 newNode ->data = newdata;

>将将新结点插在表未

 rear ->next = newNoda;(相当于 (rear->next)->next = newNode:头结点的指针域存放的是新结点的地址)

 

>尾指针指向新表尾:

 rear = newNode;

(之后结点(第2个及以后结点位置 按照2~4步骤执行))

 算法示例图:

 算法实现:

//尾插法建立单链表
 LinkList CreatListRH(void)
 {
 	DataType newdata;
 	LinkList head;
 	ListNode *rear, *newNode;//工作指针(尾指针 和 指向新结点的指针)
	head = (ListNode *)malloc(sizeof(ListNode));
	rear->next = head->next; //尾指针指向头结点
	printf("请输入链表各结点的数据(字符型):\n");
		while((newdata = getchar()) != '\n')
		{ 
		   newNode = (ListNode *)malloc(sizeof(ListNode));//向系统申请新结点的存储空间
		   newNode->data = newdata;//将读入的数据存储到新结点的数据域中
		   rear->next = newNode;   //表尾结点的指针域存放新结点的地址 
		   rear = newNode;         // 将尾指针指向新表尾(相当于 尾指针的地址和新结点一样,相当于二者指向同一存储空间) 
	    }
	 	return head;
 } 

(二)求单链表的长度:

  1. 头结点的单链表长度:

算法分析:

首先,求长度必然会涉及遍历,遍历就需要终止条件(最后一个结点指针域为NULL就是一个终止条件)

那么,①设置一个计数器count,并设置初值为:0。②设置一个移动指针p用来遍历链表。③若p->next != NULL,就使得 p 指向下一个结点(即 p = p->next),同时计数器 count + 1,直到终止条件达成(p->next == NULL)。

最后,返回的count值就是该单链表的长度。

  

  算法实现:

 // 求带头结点的单链表长度
 int LengthListH(LinkList head)
 {
 	ListNode *p = head; //移动指针指向头结点 *p = head->next 
 	int j = 0;
	 while(p->next){
	 	p = p->next; //使 p 指向下一个结点
		j++; 
	 } 
	 return j;
 } 

    2.不带 头结点的单链表长度

算法分析:

它和带 头结点的区别是,它的开始结点前面并没有头结点,所以它是直接指向开始结点,所以要考虑当前结点是否为空的情况。

当表不空时候,与带头结点操作基本相同,只是开始时 指针p指向的是开始结点,所以计数器j 的初值是1;当表空时直接返回0。

算法实现:

 //求不带头结点的单链表长度
 int LengthList(LinkList head) 
 {
 	ListNode *p = head;// p 指向开始结点
	int j;//计数器
	if(p == NULL){
		return 0;//处理空表 
	} 
	j =1;//处理非空表 
	while(p->next){
		p = p->next;//使 p 指向下一个结点
		j++; 
	}
	return j;
 }

(三)链表的查找(以带头结点为例):

  1. 序号在带头结点的单链表中查找:

算法分析:

首先,设置一个计数器j,并设置初值为0,从指针 p 指向链表的头结点开始顺序遍历,计数器随之增加变化。

接着,当 j == i(形参:传输过来的查询结点序号)时,指针 p 所指向的结点就是要找的第 i 个结点.

然鹅,当指针 p 的值为NULL(已经遍历到了最后一个结点),且 j != i时,则表示找不到第 i 个结点。

算法实现:

//按序号在带头结点的单链表中查找
ListNode *GetNode(LinkList head, int i )
//之所以函数类型是  ListNode *,是因为 使用指针 p(ListNode *类型) 找到结点后,通过 p 将找到的结点信息返回 
{
	int j = 0;//计数器
	ListNode *p = head;//从头结点开始扫描
	while(p->next != NULL && j < i){
		p = p->next;
		j++;
	} 
	if(i == j)
	   return p;//找到了第 i 个结点 
	else
	   return NULL; 
} 

      2.在带头结点的单链表中查找:

算法分析:

从开始结点出发,依次遍历单链表中各个结点的值和定值key(传输的形参)作比较,若在链表中找到与key值相同的结点,返回该结点的存储地址,否则返回NULL;

算法实现:

//按值在带头结点的单链表中查找
ListNode *LocateNode(LinkList head, DataType key)
{
	ListNode *p = head->next;//这里也可以写成 ListNode *p = head;,这样的话下面的判断条件里就是(p->next &&……) 
	while(p && p->data != key){//p 等价于p != NULL 
		p = p->next; 
	}
	return p;
}

(四)链表的插入(以带头结点为例

算法分析:

要想将新结点插入链表中,那就是将值为x的新结点 *newnode 插入单链表head的第i个结点ai的位置上。

首先,从开始结点开始遍历,找到第i-1个结点(方便对第i和第i-1个结点进行相关操作),相当于进行了 p = GetNode(head, i-1)的代码执行。

(图1)

然后,生成一个值(你想插入的值)为x的新结点 *newnode,即进行了如下代码执行:newnode = (ListNode *)malloc (sizeof(ListNode));

          //为新结点申请链表结点类型大小的空间,做到空间高效使用

          Newnode->data = x;

(图2)

最后,先将第i个结点及以后的结点,连接在新结点 *newnode 后面(相当于先将 *newnode 的指针域存放第i个结点的地址),再将新结点 *newnode接在第i- 1的结点后面(即将新结点 *newnode的地址存放在第i- 1的结点的指针域里)

(图3、4)

  算法示例图:(直接放书上例题了,懒得画了……)

算法实现:

//链表的插入(以带头结点为例)
int InsertList(LinkList head, DataType x, int i)
{
	ListNode *p, *newnode;
	newnode = GetNode(head, i-1);//寻找第 i-1 个结点
	if(p == NULL)
	{
		printf("未能找到第%d个结点", i-1);
		return 0;
	} 
	newnode = (ListNode*)malloc(sizeof(ListNode));//找到第 i-1个结点的情况下
	newnode->data = x;
	newnode->next = p->next;//将第i个结点及以后的结点,连接在新结点 *newnode 后面
	p->next = newnode;//再将新结点 *newnode接在第i- 1的结点后面
	return 1; 
}

(五)链表的删除(以带头结点为例):

算法分析:

为了删除单链表上第 i个结点。

首先,依旧是从开始结点出发,依次遍历,找到第 i-1个结点(选择这个结点是因为它的指针域里存放的值第i个结点的地址,方便两头操作),是遍历指针 p 指向它。即实现代码操作:p = GetNode(head, i-1)。

然后,使得辅佐指针 *rear指向第i个结点(被删除的结点):

rear = p->next;

接着,将第i+1个结点(被删除结点的直接后继结点)的地址存放在第 i-1个结点的指针域里(相当于第i-1个结点跨过第i个,直接连接第i+1个结点

      p->next = rear->next;

最后释放被删除结点的空间(free(rear))。

  算法示例图:(直接放书上例题了,懒得画了……)

 算法实现:

//链表的删除(以带头结点为例
int DeleteList(LinkList head, int i)
{
	ListNode *p, *rear;
	p = GetNode(head, i-1);
	if(p == NULL)
		{
			printf("未能找到第%d个结点", i-1);
			return 0;
		}
	rear = p->next;//辅佐指针 *rear指向第i个结点(被删除的结点)
	p->next = rear->next;//第i+1个结点(被删除结点的直接后继结点)的地址存放在第 i-1个结点的指针域里
	free(rear);//释放被删除结点的空间
	return 1;
}	

三、拓展 循环和双向链表:

四、项目实践(以带头结点的单链表为基础):

(一)目标:编写一个记录职工姓名信息的简易系统:

(二)目标分解:

      1、建立一个记录职工姓名信息的单链表结构体类型和单链表类型。

      2、应用1的结构体建立单链表存储信息。

      3、在2的基础上设计并输出所有职工的姓名信息。

      4、实现删除职工信息的功能。

      5、实现查找某职工姓名的功能,查找成功则返回位置信息。

 (三)项目完善:是否在上述功能编写运行无误后,给项目完善改进,如给职工信息添加性别、年龄、工号、家庭住址、电话号码等信息。然后根据工号查找职工信息,避免姓名重复带来的查找错误。      

  (四)代码示例(简易版)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 1、建立一个记录职工姓名信息的单链表结构体类型和单链表类型。
typedef struct node
{
	char data[10];       //结点是数据域为字符串(姓名)
	struct node* next;   //结点的指针域 
}ListNode;
typedef ListNode* LinkList; //定义单链表类型
 
ListNode* LocateNode(LinkList head, char *key);

// 2、应用1的结构体建立单链表存储信息。(此处用头插法建立 带头结点的单链表)
LinkList CreatList()
{
	char ch[100];//暂存姓名信息 
	LinkList head, p;//这几个都是野指针,使用的时候是需要进行动态地址申请,或直接指向一个对象地址。 
	head = (ListNode*)malloc(sizeof(ListNode));//LinkList 其实就是代替*ListNode 
	head->next = NULL;
	while(1)
	{
	   printf("\n输入 # 结束输入\n");
	   printf("请输入具体的职工姓名:");
	   scanf("%s", ch);
	   if(strcmp(ch, "#"))
	   {
	   	    if(LocateNode(head, ch) == NULL)//避免姓名重复 
			   { 
			 	strcpy(head->data, ch);
			 	p = (ListNode*)malloc(sizeof(ListNode));
			 	p->next = head;
			 	head = p; //指针指向同一地址 
			   }
			else
              printf("\n错误!输入的名称出现重复\n"); 
	    } 
	    else
	        break;   	
	}
	return head; 
} 

// 3、在2的基础上设计并输出所有职工的姓名信息。
void PrintList(LinkList head)
{
	ListNode* p;
	p = (ListNode*)malloc(sizeof(ListNode));
	p = head;
	while(p)
	{
		printf("%s\n", p->data);
		p = p->next;
	} 
	printf("\n");
} 

// 4、实现删除职工信息的功能。
void DeleteList(LinkList head, char *key)
{
	ListNode *p, *rear, *front = head;
	p = (ListNode*)malloc(sizeof(ListNode));
	p = LocateNode(head, key);
	if(p == NULL)
	{
		printf("查找位置错误");
		exit(0); 
	} 
	while(front->next != p)  // p 为要删除的结点, front 为p的前一个结点 
	{
		front = front->next;
	}
	rear = front->next;
	front->next = rear->next; //释放结点 
	free(rear);
}

// 5、实现查找某职工姓名的功能,查找成功则返回位置信息。 
ListNode* LocateNode(LinkList head, char *key)
{
	int count = 0;
	ListNode *p = head;
	while(p != NULL && strcmp(p->data, key) != 0)//遍历扫描,直至 p 为 NULL,或p->data 为 key 止 
	{
		p = p->next;
		count++;
	}
	return p;
}

//主函数
int main(int argc, char *argv[])
{
	char ch[10];
	LinkList head;
	head = CreatList();
	PrintList(head);
	
	printf("请输入要删除的职工信息: ");
	scanf("%s", ch);
	DeleteList(head, ch);
	PrintList(head);
	printf("请输入要查找的职工信息: ");
	scanf("%s",ch);
	if(LocateNode(head, ch))
	{
		printf("找到该职工信息了");
	}
	else
	  printf("未能找到该职工信息!"); 
	return 0; 
} 

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值