单向链表

单向链表

前言

个人觉得,一开始接触数据结构看一大堆的定义对初学者并不友好,那样会令人望而生畏,一开始应该是简单的定义加上对代码的详细解释。

单向链表

单向链表是链表的一种,其特点是链表的连接方向是单向的,对链表的访问要通过头部开始,依序往下读取。

一个单向链表由多个节点组成,一个节点被分成两部分:

  • 第一个部分是保存关于节点的信息,称作数据域。
  • 第二个部分是存储下一个节点的地址,称作指针域。

单向链表结构示意图

p1

其中head是链表的头指针,指向的是表头节点,该节点的数据域无内容,其指针域指向的是链表的第一个节点。

链表最后一个节点的指针域为空,用NULL表示。

既不是表头结点也不是尾节点的节点数据域和指针域都不为空。

p2

节点表示

struct node{
	int data;			// 数据域 
	struct node *next;	// 指针域 
}; 
typedef struct node Node;	 
typedef struct node* PNode;  // 节点指针 

其中data为节点保存的数据,next是节点指针类型的数据,用来保存下一个节点的地址。


链表初始化

链表初始化的内容是生成一个链表表头并返回其节点地址。如果初始化失败,则返回NULL表示初始化失败。

PNode Init(){
	
	PNode head = (PNode )malloc(sizeof(struct Node));
	if(!head)			// 申请内存空间失败的情况 
		return NULL;
	else{				// 申请成功的情况 
	 	head->next = NULL;			
		return head;
	} 
} 

数据的插入、删除、查找

插入

数据的增加操作即往链表中插入数据元素,往链表里插入元素有两种形式:

  • 不要求次序的插入,使用头插法。
  • 要求次序的精准插入,找到位置后插入。

不要求次序的插入

不要求次序的插入只需要将数据元素插入链表即可,因为单向链表只能从表头往后遍历,所以采用头插法减少插入的时间复杂度。

头插法

往链表里边插入元素的方法,可能你第一想到的是往链表的尾部直接添加元素,但是因为单向链表的特性,我们获得链表尾部的节点地址的时间复杂度是不稳定的。链表越长,获取链表尾部节点的地址就越慢。所以为了插入数据操作的高效性,我们使用头插法。

头插法,顾名思义就是直接在链表的头部插入数据。

大致操作如下:

p3
  • 申请新节点

  • 将需要插入的节点数据赋值给新节点数据域。

    p4
  • 使新节点指向表头指向的节点。

    p5 p6
  • 使表头指向新节点。

    p7 p8

将该操作写成函数,该函数的作用是往链表插入元素,插入成功返回1,失败返回0。

代码为:

int Insert(PNode rt,int data){
	// 申请新节点的空间 
	PNode NewNode = (PNode )malloc(sizeof(struct Node));
	if(!NewNode)			// 申请失败的情况 
		return 0;
	else{					// 申请成功的情况
		NewNode->data = data;		// 节点数据赋值 
		NewNode->next = rt->next;	// 令新节点的next指向表头指向的节点 
		rt->next = NewNode;			// 令表头指向新节点 
		return 1;
	} 
}

精准位置插入

将数据元素插入到第i个元素之后,如果i超过链表长度则将其插入到链表的最后位置。插入成功返回1,失败返回0。

int Insert_i(PNode rt,int i,int data){
	// 申请新节点空间
	PNode NewNode = (PNode )malloc(sizeof(struct Node));
	if(!NewNode)					// 申请空间失败的情况 
		return false; 
	else{							// 申请成功的情况 
		NewNode->data = data;		// 节点数据赋值 
		int index = 0;
		PNode pr = rt;				// pr的代表的是第i个节点的地址 
		while(pr->next != NULL){	//这个循环的作用是使pr获取第i个节点的地址 
			index++;
			pr = pr->next;
			if(index == i)break;
		}
		NewNode->next = pr->next;	//获取地址之后进行修改,类似于头插法的方式 
		pr->next = NewNode;
		return 1;
	}
} 

删除

删除操作是插入操作的逆操作,有两种形式:

  • 删除特定元素。
  • 删除特定位置的元素。

删除特定元素

找到该元素的地址和其前驱即可对该元素进行删除,删除成功返回1,失败返回0。

int Delete(PNode rt,int data){
	PNode pre = rt,add = rt;
    // 查找链表中是否含有此元素
	while(add->next != NULL){
		pre = add;
		add = add->next;
		if(add->data == data)break;
	}
	if(add->data != data || add == rt)return 0; // 未找到的情况
	else{										// 找到的情况
		pre->next = add->next;			// 直接让前驱指向第i个元素的后继
		free(add);						// 释放该节点空间
		return 1;
	}
} 

删除特定位置的元素

删除第i个位置的元素,如果i大于链表长度则删除失败返回0,成功则返回1。

int Delete_i(PNode rt,int i){
	PNode pre = rt,add = rt;
	int index = 0;
	while(add->next != NULL){
		index++;
		pre = add;
		add = add->next;
		if(index == i)break;		// 找到第i个元素
	}
	if(index != i)return 0;			//i大于链表长度或者i<= 0的情况
	else{
		pre->next = add->next;		// 直接让前驱指向第i个元素的后继
		free(add);					// 释放第i个节点
		return 1;					// 删除成功
	}
}

查找

查找操作即在链表中查找是否存在某元素,若存在返回其节点地址,不存在返回NULL。

PNode find(PNode rt,int data){
	PNode pr = rt;
	while(pr->next != NULL){
		pr = pr->next;
		if(pr->data == data)break;	// 找到的情况
	}
	if(pr->data != data || pr == rt)return NULL;	// 未找到或者链表为空的情况
	else return pr;
}

这篇文章主要介绍了单向链表中最重要的三类操作,分别是数据插入、数据删除、数据查找。

实际上还有很多其他的操作,初学阶段能学会这几种足够了,贪多嚼不烂。

一下是测试函数正确性的源代码

#include<stdio.h>
#include<stdlib.h>

struct node{
	
	int data;			// 数据域 
	struct node *next;	// 指针域 
	
}; 

typedef struct node Node;	 // 对结构体重命名,方便定义数据
typedef struct node* PNode;  // 节点指针 

PNode Init(){
	
	PNode head = (PNode )malloc(sizeof(Node));
	if(!head)			// 申请内存空间失败的情况 
		return NULL;
	else{				// 申请成功的情况 
	 	head->next = NULL;			
		return head;
	} 
} 

int Insert(PNode rt,int data){
	// 申请新节点的空间 
	PNode NewNode = (PNode )malloc(sizeof(Node));
	if(!NewNode)			// 申请失败的情况 
		return 0;
	else{					// 申请成功的情况
		NewNode->data = data;		// 节点数据赋值 
		NewNode->next = rt->next;	// 令新节点的next指向表头指向的节点 
		rt->next = NewNode;			// 令表头指向新节点 
		return 1;
	} 
}

int Insert_i(PNode rt,int i,int data){
	// 申请新节点空间
	PNode NewNode = (PNode )malloc(sizeof(Node));
	if(!NewNode)					// 申请空间失败的情况 
		return 0; 
	else{							// 申请成功的情况 
		NewNode->data = data;		// 节点数据赋值 
		int index = 0;
		PNode pr = rt;				// pr的代表的是第i个节点的地址 
		while(pr->next != NULL){	//这个循环的作用是使pr获取第i个节点的地址 
			index++;
			pr = pr->next;
			if(index == i)break;
		}
		NewNode->next = pr->next;	//获取地址之后进行修改,类似于头插法的方式 
		pr->next = NewNode;
		return 1;
	}
} 

int Delete(PNode rt,int data){
	PNode pre = rt,add = rt;
    // 查找链表中是否含有此元素
	while(add->next != NULL){
		pre = add;
		add = add->next;
		if(add->data == data)break;
	}
	if(add->data != data || add == rt)return 0; // 未找到的情况
	else{										// 找到的情况
		pre->next = add->next;			// 直接让前驱指向第i个元素的后继
		free(add);						// 释放该节点空间
		return 1;
	}
} 

int Delete_i(PNode rt,int i){
	PNode pre = rt,add = rt;
	int index = 0;
	while(add->next != NULL){
		index++;
		pre = add;
		add = add->next;
		if(index == i)break;		// 找到第i个元素
	}
	if(index != i)return 0;			//i大于链表长度或者i<= 0的情况
	else{
		pre->next = add->next;		// 直接让前驱指向第i个元素的后继
		free(add);					// 释放第i个节点
		return 1;					// 删除成功
	}
}

PNode find(PNode rt,int data){
	PNode pr = rt;
	while(pr->next != NULL){
		pr = pr->next;
		if(pr->data == data)break;	// 找到的情况
	}
	if(pr->data != data || pr == rt)return NULL;	// 未找到或者链表为空的情况
	else return pr;
}

// 遍历输出链表全部元素 
void Tra(PNode rt){
	PNode pr = rt;
	while(pr->next != NULL){
		pr = pr->next;
		printf("%d ",pr->data);
	}
	printf("\n");
}

int main(){
	
	PNode L1,L2;
	// 初始化两个链表 
	L1 = Init();
	L2 = Init();
	if(L1)printf("链表一创建成功。\n");
	if(L2)printf("链表二创建成功。\n");
	
	int i ;
	printf("L1使用头插法插入1~10:\n");
	for(i = 1; i <= 10; i++)
		Insert(L1,i);
	Tra(L1);
	printf("L2使用位置插入10~19:\n");
	for(i = 10; i <= 19; i++)
		Insert_i(L2,i-9,i);
	Tra(L2);
	
	printf("删除L1元素1、2、3、4、5:\n");
	for(i = 1; i <= 5; i++)
		Delete(L1,i);
	Tra(L1);
	printf("删除L2第10个元素:\n");
	Delete_i(L2,10);
	Tra(L2);
	return 0;
}

输出为:

链表一创建成功。
链表二创建成功。
L1使用头插法插入1~10:
10 9 8 7 6 5 4 3 2 1
L2使用位置插入10~19:
10 11 12 13 14 15 16 17 18 19
删除L1元素1、2、3、4、5:
10 9 8 7 6
删除L2第10个元素:
10 11 12 13 14 15 16 17 18

posted @ 2019-09-23 21:10  君焰w  阅读(545)  评论(0编辑  收藏  举报