对链表的简单应用:不带头链表大例题

一、链表定义

下面是摘自百度百科的解释:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
而链表基本分为:单向不循环链表,单向循环链表,双向链表,双向循环链表
下面给出带头结点链表和不带头结点链表的示意图
在这里插入图片描述
可以看出带头结点链表的头结点不存储有效信息,而不带头结点链表第一个结点就开始存储信息。下面主要讲述不带头结点链表。
我们要完成的是对链表的进行输入信息,增加,删除,排序,改变某一个结点,完成对屏幕点坐标的管理。

二、函数实现

定义一个关于点坐标的结构体。

typedef struct POINT{
 int row;
 int col;
 struct POINT *next;
}POINT;

在主函数中定义一个指类为POINT的一个指针,初始值为NULL。

POINT *pointlist1 = NULL;

为了不让主函数看起来很臃肿,具体的功能,我们应该要编写函数去完成。前面说过,我们在主函数中定义了一个POINT* 类型的指针,为了在接下来的函数调用去改变pointlist1的指向,我们应该去取得pointlist1的地址,通过指向运算,去更改pointlist1的指向,即,我们要使用二阶指针。

下面是关于输入数据,在我们输入数据之前,我们应该思考,该如何输入?输入结束的条件?储存输入的数据等等。

boolean InputPoint(POINT **head) {
	int row;
	int col;
	POINT *p;
	POINT *tail;//这个指针始终指向链表的末节点
	
	//判断head是否指向NULL或者*head即pointlist1是不是指向NULL,如果指向NULL,程序继续
	//如果不指向,说明pointlist1已经指向了某些数据,此时不能对他再进行赋值操作。
	if(NULL == head && NULL != *head) {
		return FALSE;
	}
	printf("Please enter point coordinates。\n");
	scanf("%d %d",&row,&col);
	
	//这里判断点坐标的正确性,输入小于等于零的数结束输入
	while(row > 0 && col > 0) {
		p = (POINT *)calloc(sizeof(POINT),1);
		p->row = row;
		p->col = col;
		
		//使pointlist1指向申请的第一个链表,后面的则依次相连
		if(NULL == *head) {
			*head =  p;
		} else {
			tail->next = p;
		}
		tail = p;

		scanf("%d %d",&row,&col);
	}

	return TRUE;
}

有输入结点自然也有销毁结点,不然会造成一个很严重的现象,内存泄漏,申请未释放的结点就变成了孤魂野鬼!

这里的销毁链表比较简单,让一个指针指向将要销毁的指针,而另一个指针控制向后依次移动

void DestoryPoint(POINT **head) {
	POINT *p;

	if(NULL == head && NULL == head) {
		return;
	}

	while(*head) {
		p = *head;
		*head = p->next;//完成了链表的循环
		free(p);
	}
}

当我们输入了数据存储到链表中,我们得查看这些数据是否存储成功,就需要打印链表的值,来进行判断。

void ShowPoint(POINT *head) {
	if(NULL == head) {
		return;
	}

	while(head) {
		printf("(%d,%d)\n", head->row, head->col);
		head = head->next;
	}
}

但当我们想要增加,删除或者改变某一结点时,我们要做的第一件事是什么?
我们应该先找到这个结点吧。而这些功能开始之前,这个查找的功能被多次使用,那么我们应该把他编写为一个函数,并且这个函数的返回值是指向这个结点的前一个结点的指针。

POINT * SearPreviousPoint(POINT *head) {
	int row;
	int col;
	POINT *p = head;
	POINT *pre = NULL;
	
	//在这个函数中输入点信息的原因是输入查找点坐标和查找点坐标密不可分,放在函数里面更为合适。
	scanf("%d %d",&row,&col);
	
	//这里就分了三种情况
	//第一种,pre == NULL ,即,链表的第一个结点就是我们要寻找的结点。
	//第二种,NULL == pre->next,这就说明没要找到这个结点。
	//第三种,他们的中间情况,这里pre确实指向了寻找结点的前一个结点。
	while (p) {
		if(p->row == row && p->col == col) {
			return pre;
		}
		pre = p;
		p = p->next;
	}

	return pre;
}

后面的增加,删除,改变结点就变得比较简单,毕竟我们已经找到了我们要寻找到结点。

增加结点

boolean IncreasePoint(POINT **head) {
	POINT *pre;
	POINT *p;

	if(NULL == head && NULL == *head) {
		return FALSE;
	}
	p = (POINT *)calloc(sizeof(POINT),1);

	printf("Please enter the position of the insertion point:");
	pre = SearPreviousPoint(*head);

	printf("Please enter the point you want to insert:");
	scanf("%d %d",&p->row,&p->col);

	if(NULL == pre) {
		p->next = *head;
		*head = p;
	} else {
		p->next = pre->next;
		pre->next = p;
	}

	return TRUE;
}

删除节点

boolean DeletePoint(POINT **head) {
	POINT *p;
	POINT *pre;

	if(NULL == head && NULL == *head) {
		return FALSE;
	}

	printf("Please enter the point you want to delete:");
	pre = SearPreviousPoint(*head);

	if(NULL == pre) {
		p = *head;
		*head = p->next;
	} else if (NULL == pre->next) {
		return NOT_FOUND;
	} else {
		p = pre->next;
		pre->next = p->next;
	}
	free(p);

	return TRUE;
}

改变结点的值

boolean ExchangePoint(POINT **head) {
	int row;
	int col;
	POINT *pre;

	if(NULL == head && NULL == *head) {
		return FALSE;
	}

	printf("Please enter the point you want to change:");
	pre = SearPreviousPoint(*head);

	printf("Please enter this point:");
	scanf("%d %d",&row, &col);

	if (NULL == pre) {
		(*head)->row = row;
		(*head)->col = col;
	} else if (NULL == pre->next) {
		return NOT_FOUND;
	} else {
		pre->next->row = row;
		pre->next->col = col;
	}
	return TRUE;
}

最后一个功能是排序,我们根际每一节点的行坐标进行升序排列。这里会遇到一个问题,当把两节结点交换后,他们的链域也随之交换,这是不允许的,所以我们要注意将交换后的链域再交换回来。

boolean SortPoint(POINT **head) {
	POINT *p;
	POINT *j;
	POINT *r;
	POINT tmp;

	if(NULL == head && NULL == *head) {
		return FALSE;
	}

	for(p = *head; p; p = p->next) {
		for(j = p->next; j; j = j->next) {
			if((p->row) > (j->row)) {
				tmp = *p;
				*p = *j;
				*j = tmp;
				
				//交换回链域
				r = p->next;
				p->next = j->next;
				j->next = r; 
			}
		}
	}

	return TRUE;
}

三、总结

至此,这些工具都已完成。在编写这些函数的时候,令人比较头大的就是结点与结点之间的链接,即就是对指针的灵活运用。并且,为了更改指向第一个结点的值,我们还使用了二阶指针。
总结:1.当不改变头指针的指向时,直接传头指针。但是要改变头指针的指向时,我们必须传头指针的首地址。
2.当一个功能多次使用,我们应该将它编写为函数。

在这里特别感谢铁血教主。
20200717于机房。

猜你喜欢

转载自blog.csdn.net/weixin_45483328/article/details/107405181