2.C_数据结构_线性表

news2024/12/23 6:05:52

线性表的描述

线性表就是若干数据的一个线性序列。

数学表达式:

L:表名

a0~an-1:数据元素

n:表长,n>0是为非空表

二元描述形式:

D:数据元素D用 ai 表示,这个 i 范围是0~n-1

R:关系用R表示,这个关系是<ai,ai+1>,表示ai为ai+1的直接前驱,ai+1为ai的直接后继。

<xxx1,xxx2>:这符号称为有序对,xxx1是xxx2的直接前驱,xxx2是xxx1的直接后继。

                        xxx1和xxx2可以为一个或多个。

                        当xxx1与xxx2都为1个时,这代表是1对1的关系,是逻辑结构中的线性结构

二元描述形式示例:

D代表数据元素,在这里就是1,2,3,4,5,6

R代表关系,用<xxx1,xxx2>表示各个元素之间的前驱与后继关系。

从图中可以看到1是2的前驱,2是1的后驱。所以<1,2>。后面的<2,3>....都同理。

线性表的特征:

  • 对于非空表,表头没前驱、表尾没后驱
  • 对于非空表,其他的元素ai有且仅有一个前驱ai-1和后继ai+1

顺序表的实现

1、基本内容

顺序表就是线性表的顺序存储,常用sqlist来表示。顺序存储在编程中用数组的方式来实现。

顺序表结构体如下:

#define N 100         //最大存储的数据元素个数
typedef int data_t;   //数据元素的类型,可以为int,也可以是一个结构体
//顺序表
typedef struct{
    data_t data[N];   //申请的存储空间
    int last;         //最后一个数据的索引值,data[N]是最大空间,last代表实际用了多少空间
}sqlist,*sqlink;

顺序表代码的文件构成:

  • sqlist.h:数据结构的定义、运算函数接口
  • sqlist.c:运算函数接口的实现
  • test.c:使用数据结构实现的应用功能代码

2、功能实现

2.0 编程注意点

2.0.1 防御式编程

函数编写的常用条件判断:

1、当传入参数为指针时,需要进行空指针判断

//判断顺序表传入的指针是否为空
if(pLink == NULL){
		printf("pLink is NULL\n");
		return;
}

2、当使用malloc申请内存时,需要对申请的空间进行空指针判断

//判断申请的空间是否为空
pLink = (sqlink)malloc(sizeof(sqlist));
if(pLink == NULL){
    printf("malloc failed\n");
	return NULL;
}

3、对于空间固定的结构,需要访问是判断访问下标是否在合理范围内

//在插入、删除函数中,需要判断传入的位置是否在合理范围
if( location < 0 || location > pLink->last){
	printf("location err\n");
	return -1;
}

4、对于空间固定的结构,需要在新增成员前判断是否还存在剩余空间

2.0.2 函数分类

顺序表相关函数:

1、整个表的创建和删除

  • sqlink list_create(void)
  • void list_delete(sqlink pLink)

2、 表中元素的增删改查

  • 插入:int list_insert(sqlink pLink,data_t value,int location)
  • 删除:int list_delete_data(sqlink pLink,int location)
  • 查找:int list_isexsit(sqlink pLink,data_t value)
  • 获取:data_t* list_get(sqlink pLink,int location)
  • 清空:int list_clean(sqlink pLink)

3、获取表的属性

  • 元素个数:int list_length(sqlink pLink)
  • 是否为空:int list_isempty(sqlink pLink)

4、其他

  • 删除重复元素:int list_purge(sqlink pLink)
  • 合并两个顺序表:int list_merge(sqlink pLink1,sqlink pLink2)

2.1 创建/删除表

2.1.1 创建

创建表就是申请一个空间,并给这个空间进行初始化。具体代码实现如下:

/*
 * list_create:创建一个空表
 * @ret 顺序表指针--success NULL--failed
 * */
sqlink list_create(void){
	
	sqlink pLink = NULL;
	//1.申请空间
	pLink = (sqlink)malloc(sizeof(sqlist));
	if(pLink == NULL){
		printf("malloc failed\n");
		return NULL;
	}
	//2.初始化
	memset(pLink->data,0,N*sizeof(data_t));
	pLink->last = -1;
	printf("Debug:at list_create,last = %d\n",pLink->last);
	return pLink;
}
2.1.2 删除表

删除表就是释放创建时申请的空间。具体代码实现如下:

/*
 * list_delete:删除顺序表,释放空间
 * param pLink:顺序表指针
 * */
void list_delete(sqlink pLink){
	//1.表指针判断
	if(pLink == NULL){
		printf("pLink is NULL\n");
		return;
	}
	//2.释放空间
	free(pLink);
	pLink = NULL;//这里不是想让main中的指针指向NULL
				 //当释放空间后,需要把指针指向NULL
}

2.2 元素的增删改查

2.2.1 插入

插入函数传参分析:

代码功能:

向顺序表中指定位置插入指定元素。因此需要3个变量:顺序表指针、位置、变量

函数声明如下:

int list_insert(sqlink pLink,data_t value,int location)

函数的传参需要进行有效性的判断:传入的指针是否为空、传入的位置是否符合有效范围。除此之外,因为顺序表是一个空间固定的表,所以还需要判断当前表中是否还要空余位置

插入代码分析:

假设我们想要在1位置去插入,那么首先需要去腾出一个位置,即:把1移到2,把2移到3...直到最后一个last移到last+1处。之后在1位置处去插入我们所需要的值即可。具体示意图如下:

特殊的,如果想插入的位置在last后,这样就不要去移动了,只需要去插到last+1处即可。

注意:在插入之后,last需要加一,因为多出来了一个元素。

具体代码实现如下:

/*
 * list_insert:向顺序表指定位置插入指定的数据
 * param pLink:顺序表指针
 * param value:要插入的数据
 * param location:要插入的位置
 * @ret  -1--err 0--success
 * */
int list_insert(sqlink pLink,data_t value,int location){
	
	int i;
	//1.参数有效性判断
	//1.1 表指针判断
	if(pLink == NULL){
		printf("pLink is NULL\n");
		return -1;
	}
	//1.2 位置判断
	if( location < 0 || location >= N){
		printf("location err\n");
		return -1;
	}
	//2.判断表是否为满
	if(pLink->last >= N-1){
		printf("list is full\n");
		return -1;
	}

	//3.插入数据
	if(location > pLink->last){//3.1插入位置在last后
		pLink->data[pLink->last+1] = value;
	}else{//3.2插入位置在last前
		//移出位置
		i = pLink->last;
		while(i != location-1){
			pLink->data[i+1] = pLink->data[i];
			i--;
		}
		//插入数据
		pLink->data[location] = value;
	}
	pLink->last++;
	return 0;
}
2.2.2 删除

删除函数传参分析:

代码功能:

从顺序表中指定位置删除指定元素。因此需要2个变量:顺序表指针、位置

函数声明如下:

int list_delete_data(sqlink pLink,int location)

函数的传参需要进行有效性的判断:传入的指针是否为空、传入的位置是否符合有效范围

删除代码分析:

假设我们想要删除1位置的数据,那么只需要把后面的数据覆盖1位置即可,即:把2移到1,把3移到2...直到最后一个last移到last-1处。之后去清除原last位置处的数据。具体示意图如下:

注意:在插入之后,last需要减一,因为少了一个元素。

具体代码实现如下:

/*
 * list_delete_data
 * param pLink:顺序表指针
 * param location:要删除的数据位置
 * @ret  -1--err  0--success
 * */
int list_delete_data(sqlink pLink,int location){
	int i;
	//1.参数有效性判断
	//1.1 表指针判断
	if(pLink == NULL){
		printf("pLink is NULL\n");
		return -1;
	}
	//1.2 位置判断
	if( location < 0 || location > pLink->last){
		printf("location err\n");
		return -1;
	}

	//2.删除元素
	i=location;
	while(i!=pLink->last){
		pLink->data[i] = pLink->data[i+1];
		i++;
	}
	memset(&(pLink->data[pLink->last]),0,sizeof(data_t));
	pLink->last--;
	return 0;
}
2.2.3 查询

查询函数传参分析:

代码功能:

查询某个元素是否在顺序表中,并返回第一个出现的下标。因此需要2个变量:顺序表指针、位置

函数声明如下:

int list_isexsit(sqlink pLink,data_t value)

函数的传参需要进行有效性的判断:传入的指针是否为空。

具体代码实现如下:

/*
 * list_isexsit:判断顺序表中是否有某个元素
 * param pLink:顺序表指针
 * @ret  -2--err 
 * @ret  -1--not exsit  other--第一个找到的元素位置
 * */
int list_isexsit(sqlink pLink,data_t value){
	int i=0;
	//1.表指针判断
	if(pLink == NULL){
		printf("pLink is NULL\n");
		return -2;
	}
	//2.遍历查找元素
	for(i=0;i<=pLink->last;i++){
		if(pLink->data[i] == value){
			//printf("find it\n");
			return i;
		}
	}
	//printf("not find\n");
	return -1;
}
2.2.4 获取

获取函数传参分析:

代码功能:

获取顺序表中指定位置的元素地址。因此需要2个变量:顺序表指针、位置

函数声明如下:

data_t* list_get(sqlink pLink,int location)

函数的传参需要进行有效性的判断:传入的指针是否为空、传入的位置是否符合有效范围

具体代码实现如下:

/*
 * list_get:获取顺序表中指定的数据的指针
 * param pLink:顺序表指针
 * param location:要获取数据的位置
 * @ret  NULL--failed  找到的数据的指针--success
 * */
data_t* list_get(sqlink pLink,int location){
	
	data_t* point = NULL;
	//1.参数有效性判断
	//1.1 表指针判断
	if(pLink == NULL){
		printf("pLink is NULL\n");
		return NULL;
	}
	//1.2 位置判断
	if( location < 0 || location >= N){
		printf("location err\n");
		return NULL;
	}
	//2.获取顺序表中指定的数据的指针
	if(location > pLink->last){//获取位置没有数据
		//printf("no data here \n");
	}
	point = &pLink->data[location];
	return point;
}
2.2.5 清空

具体代码实现如下:

/*
 * list_clean:清空顺序表
 * param pLink:顺序表指针
 * @ret  -1--err  0--success
 * */
int list_clean(sqlink pLink){
	//1.表指针判断
	if(pLink == NULL){
		printf("pLink is NULL\n");
		return -1;
	}
	//2.清除表
	memset(pLink->data,0,N*sizeof(data_t));
	pLink->last = -1;
	return 0;	
}

2.3 获取表的属性

2.3.1 元素个数
/*
 * list_length:获取顺序表的长度
 * param pLink:顺序表指针
 * @ret  -1--err other--长度 
 * */
int list_length(sqlink pLink){
	//1.表指针判断
	if(pLink == NULL){
		printf("pLink is NULL\n");
		return -1;
	}
	//2.返回长度
	return (pLink->last+1);
}
2.3.2 是否为空
/*
 * list_isempty:判断表是否为空
 * param pLink:顺序表指针
 * @ret  -1--err  0--not empty 1--empty 
 * */
int list_isempty(sqlink pLink){
	//1.表指针判断
	if(pLink == NULL){
		printf("pLink is NULL\n");
		return -1;
	}
	//2.判断是否非空
	if(pLink->last == -1){
		printf("sqlist empty\n");
		return 1;
	}else{
		printf("sqlist not empty\n");
		return 0;
	}
}

2.4 其他

2.4.1 删除重复元素
/*
 * list_purge:删除重复的元素
 * param pLink:顺序表指针
 * @ret -1--err 0--success
 * */
int list_purge(sqlink pLink){
	int i;
	int FindId;
	//1.表指针判断
	if(pLink == NULL){
		printf("pLink is NULL\n");
		return -1;
	}
	//2.元素少于2个时候,不需要处理
	if(pLink->last <= 0){
		return 0;
	}
	//3.开始删除
	for(i=1;i<=pLink->last;i++){
		//找到第一个出现的位置
		FindId = list_isexsit(pLink,pLink->data[i]);
		//找到了重复的值,第一次位置与当前位置不一样
		if(FindId != i){
			list_delete_data(pLink,i);
			i--;
		}		
	}
	return 0;
}
2.4.2 合并两个顺序表
/*
 * list_merge:将顺序表1和顺序表2进行合并
 *            表2不变,表1中添加表2中的非同类项
 * param pLink:顺序表指针
 * @ret -1--err 0--success
 * */
int list_merge(sqlink pLink1,sqlink pLink2){
	int i=0;
	//1.表指针判断
	if(pLink1 == NULL || pLink2 == NULL){
		printf("pLink is NULL\n");
		return -1;
	}
	//2.遍历2,查找是否在1中,不在则追加到尾部
	for(i=0;i<=pLink2->last;i++){
		//1满了,提前退出插入
		if(pLink1->last == N-1){
			printf("sqlist1 full\n");
			break;
		}
		//如果2中的数据1中不存在,则插入到尾部
		if(list_isexsit(pLink1,pLink2->data[i]) == -1){
			list_insert(pLink1,pLink2->data[i],pLink1->last+1);
		}		
	}
	return 0;
}

链表的实现

1、基本内容

链表就是线性表的链式存储,常用listnode来表示。链式存储在编程中用指针链接的方式来实现。

链表结构体如下:

typedef int data_t;   //数据元素的类型,可以为int,也可以是一个结构体
//链表
typedef struct node{
    data_t data;        //数据
    struct node* pNext; //链表指针,指向下一个链表结点
}listnode,*linklist;

链表代码的文件构成:

  • listnode.h:数据结构的定义、运算函数接口
  • listnode.c:运算函数接口的实现
  • test.c:使用数据结构实现的应用功能代码

2、功能实现

2.0 编程注意点

2.0.1 链表移动注意点

链表移动、插入时插入考虑三种情况:

  • 插入点前没有结点(最前面)
  • 插入点前后都有结点(中间)
  • 插入点后面没有结点(最后面)

插入之前需要保存现场:保存前一个结点和后一个结点的位置,这样才能保证链表不丢失。

2.0.2 函数分类

链表相关函数:

1、整个表的创建和删除

  • 创建:linklist list_create(void)
  • 删除:int list_delete(linklist* pLink)

2、 表中元素的增删改查

  • 尾插:int list_insert_tail(linklist pLink,linklist newLink)
  • 指定位置插入:int list_insert_location(linklist* pLink,linklist newLink,int location)
  • 删除:int list_delete_location(linklist* pLink,int location)
  • 获取:linklist list_get(linklist pLink,int location)

3、其他

  • 链表反转:int list_reversal(linklist* pLink)
  • 获取相邻两个结点数据之和最大值的指针:linklist get_MaxSumNode(linklist pLink)
  • 合并两个有序链表:linklist list_merge(linklist pLink1,linklist pLink2)
  • 链表排序:linklist list_sort(linklist pLink)

2.1 创建/删除链表

2.1.0 创建

创建链表就是申请一个空间,并给这个空间进行初始化。对于新链表,pNext应该指向NULL。

具体代码实现如下:

/*
 * list_create:创建一个链表结点
 * @ret  NULL--创建失败  other--创建的链表结点首地址
 * */
linklist list_create(void){
	
	linklist pLink = NULL;
	//1.申请空间
	pLink = (linklist)malloc(sizeof(listnode));
	if(pLink == NULL){
		printf("malloc failed\n");
		return NULL;
	}
	//2.初始化
	memset(&pLink->data,0,sizeof(data_t));
	pLink->pNext = NULL;
	return pLink;
}
2.1.1 删除

删除整个链表就是用遍历的方式释放每一个链表结点。最后要把释放后的链表头变为NULL。

具体代码实现如下:

/*
 * list_delete:释放整个链表
 * param pLink:链表头
 * @ret  -1--err  0--success
 * */
int list_delete(linklist* pLink){
	linklist point = *pLink;
	linklist pTmp = NULL;
	//1.判断结点非空
	if(*pLink == NULL){
		printf("listnode is NULL\n");
		return -1;
	}
	//2.释放整个链表
	while(point!=NULL){
		pTmp = point;
		point = point->pNext;
		free(pTmp);
	}
	*pLink = NULL;//释放之后需要把头结点设为空
	return 0;
}

2.2 元素的增删改查

2.2.1 尾插

尾插法比较简单,首先需要遍历链表找到尾部,之后让尾部指向新的链表即可。如果想提高效率,可以定义一个尾结点,这样就不需要每次遍历寻找尾部了。

具体代码实现如下:

/*
 * list_insert_tail:尾部插入链表结点
 * param pLink:插入哪一个链表
 * param newLink:要插入的链表结点
 * @ret  -1--err  0--success
 * */
int list_insert_tail(linklist pLink,linklist newLink){
	
	linklist point = pLink;
	//1.判断结点非空
	if(pLink == NULL || newLink == NULL){
		printf("listnode is NULL\n");
		return -1;
	}
	//2.开始插入
	//2.1 找到尾部
	while(point->pNext != NULL){
		point = point->pNext;
	}
	//2.2插入新结点
	point->pNext = newLink;
	return 0;
}
2.2.2 指定位置插入

指定位置插入就是按照所给的序号进行插入,这里需要考虑三种情况:

  • 插入点前没有结点(最前面)
  • 插入点前后都有结点(中间)
  • 插入点后面没有结点(最后面)

 具体框图如下:

情况1的插入方式特殊,需要将头进行改变。情况2和情况3都是将链表插入到p1,p2之间即可。

具体的插入条件与步骤如下:

从上述分析可以看到,进行复杂插入的关键是保存插入点前后的结点位置。在下面的代码中并没有使用单独的p1,p2来保存插入点前后的结点位置,但逻辑依旧与上面的分析一致。

具体的代码实现如下:

/*
 * list_insert_location:在指定的序号元素后插入结点
 * 						-1代表在头前面插入
 * param pLink:要在哪里插入结点
 * param newLink:插入的新结点
 * param location:插入的位置
 * @ret  -1--err  0--success
 * */
int list_insert_location(linklist* pLink,linklist newLink,int location){
	linklist point = NULL;
	linklist pNextTmp = NULL;
	//1.参数有效性判断
	//1.1判断结点非空
	if(*pLink == NULL || newLink == NULL){
		printf("listnode is NULL\n");
		return -1;
	}
	//1.2 判断位置
	if(location < -1){
		printf("location err\n");
		return -1;
	}
	//2.特殊位置插入,头前面插入
	if(location == -1){
		newLink->pNext = *pLink;
		*pLink = newLink;
		return 0;
	}
	//3.获取位置
	point = list_get(*pLink,location);
	//4.在指定位置后,插入新结点
	if(point != NULL){
		pNextTmp = point->pNext;//p2保存后结点位置
		point->pNext = newLink; //p1就是point
		newLink->pNext = pNextTmp;//new指向p2
	}else{
		list_insert_tail(*pLink,newLink);
	}
	return 0;
}
2.2.3 删除

删除指定位置的链表结点就是释放空间并链接前后的结点。(删除与"2.2.2插入"的思想完全一致)

  1. 首先需要遍历找到这个结点,遍历同时使用p1,p2保存删除点前后的结点位置
  2. 之后将前后结点链接,并释放要删除的结点的空间。
/*
 * list_delete_location:删除指定位置的链表结点
 * param pLink:要在哪里寻找结点
 * param location:结点的序号
 * @ret -1--err 0--success
 * */
int list_delete_location(linklist* pLink,int location){
	linklist point = *pLink;
	linklist pTmp = NULL;
	int i = 0;
	//1.参数有效性判断
	//1.1判断结点非空
	if(*pLink == NULL){
		printf("listnode is NULL\n");
		return -1;
	}
	//1.2位置判断
	if(location < 0){
		printf("location err\n");
		return -1;
	}
	//2.获取结点
	while(point!=NULL){
		if(i == location){
			break;
		}
		pTmp = point;
		point = point->pNext;
		i++;
	}
	if(point == NULL){//没找到
		printf("not find\n");
		return -1;
	}
	//3.开始删除
	if(point->pNext == NULL){//要删除的是尾结点
		pTmp->pNext = NULL;
		free(point);
		point = NULL;
	}else if(pTmp == NULL){//要删除的是头结点
		*pLink = point->pNext;//改变头的位置
		free(point);
		point = NULL;
	}else{//要删除的是中间结点
		pTmp->pNext = point->pNext;
		free(point);
		point = NULL;
	}
	return 0;
}
2.2.4 获取

按序号寻找链表结点就是遍历链表,第一个结点是序号0。

每次遍历比对序号,找到返回指针即可。

具体函数实现如下:

/*
 * list_get:按序号寻找链表结点
 * param pLink:要在哪里寻找结点
 * param location:结点的序号
 * @ret  NULL--not find或者err  other--第一个结点首地址
 * */
linklist list_get(linklist pLink,int location){
	
	int i=0;
	linklist point = pLink;
	//1.参数有效性判断
	//1.1判断结点非空
	if(pLink == NULL){
		printf("listnode is NULL\n");
		return NULL;
	}
	//1.2位置判断
	if(location < 0){
		printf("location err\n");
		return NULL;
	}
	//2.遍历寻找
	while(point!=NULL){
		if(i==location){//找到了
			break;
		}
		i++;
		point = point->pNext;
	}
	if(point == NULL){
		printf("not find\n");
	}
	return point;
}

2.3 其他

2.3.1 链表反转

链表反转就是将头变成尾,将尾变成头。

  1. 首先需要把头1与之后的链子断开
  2. 之后将断开的链子的头2插入到头1的前面即可。

具体框图如下:

具体代码实现如下:

/*
 * list_reversal:链表反转
 * param pLink:链表头指针
 * @ret  -1--err  0--success
 * */
int list_reversal(linklist* pLink){
	linklist pHead = *pLink;
	linklist pInTmp = NULL;
	linklist pTmp = NULL;
	//1.判断结点非空
	if(*pLink == NULL){
		printf("listnode is NULL\n");
		return -1;
	}
	//2.判断是否为单个结点
	if((*pLink)->pNext == NULL){
		printf("just have 1 node\n");
		return 0;
	}
	//3.断开头与之后数据,pInTmp暂存第2个结点,这也将要插入的结点
	pInTmp = pHead->pNext;
	pHead->pNext = NULL;
	//4.将结点进行插入
	while(1){
		pTmp = pInTmp->pNext;//暂存第3个结点
		pInTmp->pNext = pHead;//第2个结点指向第1个
		pHead = pInTmp;//头部变成了第2个结点
		pInTmp = pTmp;//插入结点变成第3个
		if(pTmp == NULL){
			*pLink = pHead;
			break;
		}
	}
	return 0;
}
2.3.2 获取相邻两个结点数据之和最大值的指针

获取相邻两个结点数据之和最大值的指针就是移动两个指针,不断比较求和值,如果后面的大于前面的,就保存后面的结点位置即可。

具体代码实现如下:

/*
 * get_MaxSumNode:获取相邻两个结点数据之和最大的第一个结点的指针
 * param pLink:要在哪里寻找结点
 * @ret  NULL--err  other--第一个结点首地址
 * */
linklist get_MaxSumNode(linklist pLink){
	int sum = 0;
	linklist point = NULL;
	linklist pRet = pLink;
	//1.判断结点非空
	if(pLink == NULL){
		printf("listnode is NULL\n");
		return NULL;
	}
	//2.判断是否为单个结点
	if(pLink->pNext == NULL){
		printf("just have 1 node\n");
		return NULL;
	}
	//3.开始计算
	point = pLink;
	sum = point->data + point->pNext->data;//初始化sum值
	while(point->pNext != NULL){
		if( sum < (point->data + point->pNext->data) ){
			sum = point->data + point->pNext->data;
			pRet = point;
		}
		point = point->pNext;
	}
	return pRet;
}
2.3.3 合并两个有序链表

合并两个有序链表是将两个递增有序的链表合并为一个递增有序的链表。

  1. 首先需要找到头部,即两个链表的头中较小的那个结点为头部。
  2. 之后随便遍历一个链表,遍历对两个链表的数值进行比较,谁小谁就插入到头后面,进行插入的链表需要将指针进行移动,未插入的链表指针不用移动。
  3. 最后判断是哪一个链表没有遍历完,将整个链表全部尾插即可。

具体的代码实现如下:

/*
 * list_merge:将两个递增有序的链表合并为一个递增有序的链表
 * param pLink1:第一个链表
 * param pLink2:第二个链表
 * @ret  NULL--err  other--合成后的链表的头
 * */
linklist list_merge(linklist pLink1,linklist pLink2){
	linklist pHead = NULL;
	linklist pTail = NULL;
	//1.判断结点非空
	if(pLink1 == NULL || pLink2 == NULL){
		printf("listnode is NULL\n");
		return NULL;
	}
	//2.确定头部,谁小谁就是头部
	if(pLink1->data < pLink2->data){
		pHead = pLink1;
		pLink1 = pLink1->pNext;//向后移动一个,指向下一个将要插入的结点
	}else{
		pHead = pLink2;
		pLink2 = pLink2->pNext;//向后移动一个,指向下一个将要插入的结点
	}
	pTail = pHead;
	//3.比较大小,谁小谁进行尾插
	while(pLink1!=NULL && pLink2!=NULL){
		if(pLink1->data < pLink2->data){
			pTail->pNext = pLink1;
			pTail = pLink1;
			pLink1 = pLink1->pNext;
		}else{
			pTail->pNext = pLink2;
			pTail = pLink2;
			pLink2 = pLink2->pNext;
		}
	}
	//4.将剩余元素进行尾插
	if(pLink1 == NULL && pLink2 != NULL){
		pTail->pNext = pLink2;
	}
	if(pLink2 == NULL && pLink1 != NULL){
		pTail->pNext = pLink1;
	}
	return pHead;
}
2.3.4 链表排序

这个链表排序是练习链表使用的,使用的方法是将冒泡排序法。

关键点是交换时前后结点的位置保存、还有交换后的指针移动。

具体代码实现如下:

/*
 * list_sort:链表排序
 * param pLink:要进行排序的链表头
 * @ret  NULL--err  other--排序后的链表头
 * */
linklist list_sort(linklist pLink){
	linklist pHead = pLink;
	linklist p1 = NULL;
	linklist p2 = NULL;
	linklist pLast1 = NULL;
	linklist pTmp2 = NULL;
	int link_len = 0;
	int i,j;
	//1.判断结点非空
	if(pLink == NULL){
		printf("listnode is NULL\n");
		return NULL;
	}
	//2.获取链表长度
	p1 = pHead;
	while(p1!=NULL){
		link_len++;
		p1 = p1->pNext;
	}
	//printf("Debug:link_len = %d\n",link_len);
	if(link_len < 3){
		return pHead;
	}
	//3.开始排序,冒泡排序
	for(i=0;i<link_len-1;i++){
		pLast1 = NULL;
		p1 = pHead;
		p2 = pHead->pNext;
		//printf("****************head data = %d*************\n",pHead->data);
		for(j=0;j<link_len-1-i;j++){
			if(p1->data > p2->data){
				//p1在头部
				if(pLast1 == NULL){
					//printf("=========p1在头部 start==============\n");
					//printf("%d %d %d %d\n",pHead->data,pHead->pNext->data,pHead->pNext->pNext->data,pHead->pNext->pNext->pNext->data);	
					//更改头部
					pHead = p2;
					//printf("data = %d\n",pHead->data);
					//开始链接
					pTmp2 = p2->pNext;
					p2->pNext = p1;
					p1->pNext = pTmp2;
					//printf("==========link=============\n");
					//printf("%d %d %d %d\n",pHead->data,pHead->pNext->data,pHead->pNext->pNext->data,pHead->pNext->pNext->pNext->data);	
					//指针移动
					pLast1 = p2;
					p1 = p1;
					p2 = p1->pNext;
					//printf("===========p move============\n");
					//printf("pLast1:%d p1:%d p2:%d\n",pLast1->data,p1->data,p2->data);	
					//sleep(1);
				}
				//p1在中间
				else{
					//printf("\n=========p1在中间start==============\n");
					//printf("%d %d %d %d\n",pHead->data,pHead->pNext->data,pHead->pNext->pNext->data,pHead->pNext->pNext->pNext->data);	
					//开始链接
					pLast1->pNext = p2;
					pTmp2 = p2->pNext;
					p2->pNext = p1;
					p1->pNext = pTmp2;
					//printf("==========link=============\n");
					//printf("%d %d %d %d\n",pHead->data,pHead->pNext->data,pHead->pNext->pNext->data,pHead->pNext->pNext->pNext->data);	
					//指针移动
					pLast1 = p2;
					p1 = p1;
					p2 = p1->pNext;
					//printf("===========p move============\n");
					//printf("pLast1:%d p1:%d \n",pLast1->data,p1->data);
					//sleep(1);
				}
			}else{
				//printf("\n=========不改变位置start==============\n");
				//printf("%p %p %p %p\n",pHead,pHead->pNext,pHead->pNext->pNext,pHead->pNext->pNext->pNext);	
				pLast1 = p1;
				p1 = p2;
				p2 = p2->pNext;
				//printf("===========p move============\n");
				//printf("%p %p %p %p\n",pLast1,p1,p2,p2->pNext);
				//sleep(1);
			}
		}
	}	

	return pHead;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2112449.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++从练气到飞升】21---再谈哈希算法:位图 | 布隆过滤器 | 哈希切分

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书&#x1f389; 目录 ⛳️推荐 一、位图 1.1 一道面试题 1.2 位图的概念 1.3 位图的模拟实现 1.4 位图的应用 1.4.1 给定100亿…

双项第一!鼎捷强势领跑PLM市场

近日&#xff0c;国际数据公司IDC发布了《中国PLM市场分析及厂商份额&#xff0c;2023&#xff1a;创新左移》 报告数据显示鼎捷PLM2023年收入增长率39.5%&#xff0c;收入增速市场第一 鼎捷在多个细分行业市场中保持领先&#xff0c;在装备制造PLM领域市场份额达到7.9%市占率…

基于 rt-thread的I2C操作EEPROM(AT24C02)

一、AT24C02 The AT24C01A/02/04/08A/16A provides 1024/2048/4096/8192/16384 bits of serial electrically erasable and programmable read-only memory (EEPROM) organized as 128/256/512/1024/2048 words of 8 bits each.AT24C01A/02/04/08A/16A提供1024/2048/4096/8192…

Redis进阶(三)--Redis高性能底层原理

文章目录 第三章、Redis高性能底层原理一、持久化1、RDB&#xff08;1&#xff09;给哪些内存数据做快照?&#xff08;2&#xff09;RDB文件的生成是否会阻塞主线程&#xff08;3&#xff09;bgsave执的行流程&#xff08;4&#xff09;RDB文件&#xff08;5&#xff09;RDB的…

ios免签H5

1、windows下载mobileconfig文件制作工具&#xff0c;可在csdn搜索iPhone_Mobileconfig_Tool下载安装&#xff1b;IOS 从APP Store 下载Apple Configurator 2 2、用申请的域名SSL证书给mobieconfig文件签名&#xff0c;最好下载Apache证书&#xff0c;里面包含 AE86211.crt…

zabbix-高级应用(主被动监控、邮件告警、企业微信告警)

文章目录 zabbix-高级应用监控路由器交换机SNMP简单网络管理协议测试案例配置网络设备创建主机创建监控项测试监控项 自动发现什么是自动发现Discovery&#xff1f;配置自动发现1、创建自动发现规则2、创建Action动作&#xff08;发现主机后自动执行什么动作&#xff09;3、通过…

Python画笔案例-037 绘制彩色格子台阶

1、绘制彩色格子台阶 通过 python 的turtle 库绘制彩色格子台阶&#xff0c;如下图&#xff1a; 2、实现代码 绘制彩色格子台阶&#xff0c;以下为实现代码&#xff1a; """彩色格子台阶.py """ import turtle from random import randomturtle…

小杨做题c++

题目描述 为了准备考试&#xff0c;小杨每天都要做题。第1天&#xff0c;小杨做了a道题;第2天&#xff0c;小杨做了b道题;从第3天起&#xff0c;小杨每天做的题目数量是前两天的总和。 此外&#xff0c;小杨还规定&#xff0c;当自己某一天做了大于或等于m题时&#xff0c;接下…

KRTSt内嵌Lua脚本

KRTSt内嵌Lua脚本 Lua 简介 Lua是一门强大、高效、轻量、可嵌入的脚本语言。它支持多种编程架构&#xff1a;过程编程、面向对象编程&#xff08;OOP&#xff09;、函数式编程、数据驱动编程及数据描述。 Lua结合了简洁的过程语法和强大的数据描述结构&#xff08;基于关联数…

什么是网络准入控制系统?网络准入控制系统七大品牌介绍!

在当今信息化时代&#xff0c;企业网络安全面临着前所未有的挑战。网络准入控制系统&#xff08;NAC, Network Access Control&#xff09;作为一种重要的网络安全技术&#xff0c;扮演着守护企业网络安全大门的关键角色。网络准入控制系统通过对接入网络的设备进行身份验证、安…

为什么现在不建议去电力设计院?终于有人把电力设计院说清楚了!

作者&#xff1a;电气哥 最近电气哥收到了许多面临就业的同学特别是硕士同学有关于电力设计院的咨询&#xff0c;那么现在电力设计院到底还值不值得去&#xff1f;电气哥带你来分析一下电力设计院的前世今生。 01 电力设计院的前世今生 曾经&#xff0c;在我国的大基建时代&…

java设计模式--(行为型模式:策略模式、命令模式、责任链模式)

6&#xff0c;行为型模式 行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务&#xff0c;它涉及算法与对象间职责的分配。 行为型模式分为类行为模式和对象行为模式&#xff0c;前者采用继承…

keepalived和lvs高可用集群

keepavlied和lvs高可用集群搭建 主备模式&#xff1a; 关闭防火墙和selinux systemctl stop firewalld setenforce 0部署master负载调度服务器 zyj86 安装ipvsadm keepalived yum install -y keepalived ipvsadm修改主节点配置 vim /etc/keepalived/keepalived.conf! Conf…

鹰眼雾炮适合在哪些场合使用

朗观视觉鹰眼雾炮由于其独特的功能和优势&#xff0c;适合在多种场合使用&#xff0c;主要包括但不限于以下几个方面&#xff1a; 建筑工地&#xff1a;建筑工地是粉尘污染的主要来源之一。鹰眼雾炮可以有效降低施工过程中的扬尘&#xff0c;改善工地及周边空气质量&#xff0c…

南卡科技“满分之选”全新开放式耳机发布,打造超越Pro的极致体验!

在音频技术的不断革新中&#xff0c;南卡品牌以其深厚的声学底蕴和对创新的不懈追求&#xff0c;再次为市场带来惊喜。今天&#xff0c;我们自豪地宣布&#xff0c;南卡OE Pro2开放式蓝牙耳机正式亮相&#xff0c;它不仅代表了南卡在开放式耳机领域的技术巅峰&#xff0c;更是对…

基本和复合逻辑运算

目录 基本逻辑运算 与运算 或运算 非运算 复合逻辑运算 与非运算 或非运算 异或运算 同或运算 基本逻辑运算 与运算 两个都为1才为1&#xff0c;否则为0&#xff0c;类似于编程语言里的&。 有0出0&#xff0c;全1出1。 逻辑表达式就是AB,可以省略中间的点。 逻辑符…

TimescaleDB-3 超表的维护

数采系统上线半年多了&#xff0c;产生了大概2亿条记录&#xff0c;这些数据其实是有时效性的&#xff0c;用来生成的二次数据可以永久保存&#xff0c;这种时序数据没有多大价值&#xff0c;又非常占用空间&#xff0c;所以定期清理超表的trunk是必要的 --1、查看分区表以及…

好用的AI编程助手[豆包]

欢迎来到 Marscode 的世界&#xff01;这里将为你揭秘 Marscode&#xff0c;它的独特之处、应用领域等相关精彩内容等你来探索。 一、打开VS Code 二、选择 Extensions,搜索marscode 三、点击安装 四、点击使用 五、输入需要编写的代码 六、根据自己的需求修改代码 MarsCode 注…

Pytorch多GPU分布式训练代码编写

Pytorch多GPU分布式训练代码编写 一、数据并行 1.单机单卡 模型拷贝 model.cuda() 原地操作 数据拷贝&#xff08;每步&#xff09; datadata.cuda() 非原地操作 基于torch.cuda.is_available()来判断是否可用 模型保存与加载 torch.save 来保存模型、优化器、其他变量tor…

spring security 中的授权使用

一、认证 身份认证&#xff0c;就是判断一个用户是否为合法用户的处理过程。Spring Security 中支持多种不同方式的认证&#xff0c;但是无论开发者使用那种方式认证&#xff0c;都不会影响授权功能使用。因为 SpringSecurity 很好做到了认证和授权解耦。 二、授权 授权&#x…