数据结构——链表的c实现(1)

链表产生的原因:

对于比较初级的编程学习者,在存储数据时,比较熟悉的是利用数组进行存储。

但是静态数组需要在存储时指定需要存储的数量,这在生产生活中不具有现实意义,因此,可以使用动态数组的方法(malloc函数)申请内存,这是可以的。

但是使用动态数组存在以下几个问题:

1. 当数据达到一定量,需要重新申请内存,然后将原来的数据复制到新的内存空间中,数据的复制需要时间,然后还需要将原先的数据释放;

2. 内存中会存在这样一种情况:明明还有内存剩余,但是由于新申请的内存空间大于剩余内存空间,无法申请成功,因此内存空间不能得到有效使用。

因此动态数组也具有一定的局限性。基于此,人们想出链表的方法,解决数据存储和内存之间的问题。

链表存储数据的原理:

链表的存储原理如下:

首先需要定义一个链表的结点:包含本结点的数据和指向下一结点的指针。类似于“数据+引线”,数据可以存储该节点的数据,而引线可以指向下一结点,本节点的数据也可以被上一引线所指。

链表的c实现:

下面的代码展示链表的c实现:

首先需要定义链表的node,即结点。根据链表的原理,应该包含一个值(value)和指向下一个结点的指针(*next),如下面的程序,定义了用户自定义的结构体,包含以上两个部分。这里,将节点的定义写在了.h的头文件中。

#ifndef _NODE_H
#define _NODE_H

typedef struct _node{
	int value;
	struct _node *next;
}Node;

#endif

结点定义好之后,就可以存储数据了。首先需要将头结点初始化

Node *head=NULL;

然后将用户输入的数据存储到链表中,这里我们假设一直存储用户输入的数据,直到用户输入-1,停止存储数据。因此程序伪代码可以写成如下形式:

do{
		scanf("%d",number);
		if(number!=-1){
			//将用户输入数据存储到链表中
	}while(number!=-1);
下面重点的就是这句注释了:
//将用户输入数据存储到链表中
我们想一想,将数据存入链表好像存在两个无法回避的问题: 什么时候可以将数据存在里面,以及如何将数据存入里面。

第一个问题至关重要,因为这可能导致后面存储数据的方式不同(例如,只有头结点的情况下和在头结点后面的结点来存储数据还不太一样)。另外,数据也不是一直都可以存进去的,比如这里有-1输入的限制。即使没有这个限制,也应该加一个如果申请不到内存了,这种情况的处理办法,是警告用户无法再进行存储还是舍去最前面的数据,存储后来的数据。

根据上面的分析,可以继续精化上面的伪代码:

对于问题1:什么时候可以将数据存在里面

//若是头结点,就直接将数据存储到头结点,否则,需要存储到尾结点

对于问题2:如何将数据存入里面

//需要新申请结点大小的内存空间,然后将数据存到里面,在将原来最后的结点指针指向新的结点。
精化之后,可以形成以下的思路: 先将数据存入链表的结点(申请内存,存储数据),再将链表尾结点的指针指向新存入数据的结点的数据,而新存入数据的指针指向null。这样就可以完成一个结点连接到链表尾部的过程。

先来看将数据存入链表的结点

这里主要有两个步骤:申请结点内存,存储结点数据。

申请结点内存时需要知道结点内存的大小,因此需要malloc(size(Node)),申请完之后需要类型转化成结点类型,因此代码如下:

Node *p=(Node*)malloc(sizeof(Node));

这里申请的结点定义为指针类型的结点p。这里p是该结点的指针,结点p里面包含本结点的数据p->value和指向下一节点指针p->next。根据刚才精化形成的思路,我们将输入的数据赋给p->value,并将p->next指向null。代码如下:

//add to linked-list
			Node *p=(Node*)malloc(sizeof(Node));
			p->value=number;
			p->next=NULL;
这里number在使用之前需要先进行类型定义。

将链表尾结点的指针指向新存入数据的结点的数据

在将原来链表的尾结点指向新结点之前需要首先知道尾结点在哪里,这里需要使用遍历来寻找尾结点。首先,我们需要定义尾结点last,并将头结点head赋给尾结点last,接着需要遍历其他结点。遍历的方法是看尾结点部分指向下一节点的指针是否是null,如果非空,则继续遍历,把last->next赋给last,否则,就找到了尾结点。此时需要将本是null的last->next指向p。

代码如下:

//Find the last
			Node *last=head;
				while(last->next){
					last=last->next;
				}
				last->next=p;
这里我们默认头结点是存在的,只要头结点存在,无论头结点后面有没有其他结点,尾结点就一定会存在,后面的操作都比较符合常理。但是很可能头结点都不存在,也就是用户输入前不存在这个链表,因此用户输入的数字应该是这个链表的头结点,即head=p。因此在将
Node *last=head;

之后,需要增加一个head或者说last是否存在的判断,存在,则进行上面的遍历;否则,直接将用户输入数据的新结点为头结点。因此完善的代码如下:

//Find the last
			Node *last=head;
			if(last){
				while(last->next){
					last=last->next;
				}
				//attach
				last->next=p;
			} else{
				head=p;
			}
这样,就完成了一个链表的创建。完整代码如下:
#include <stdio.h>
#include <stdlib.h>
#include "node.h"

/* run this program using the console pauser or add your own getch, system("pause") or input loop */

int main(int argc, char *argv[]) {
	Node *head=NULL;
	int number;
do{
		scanf("%d",number);
		if(number!=-1){
			//add to linked-list
			Node *p=(Node*)malloc(sizeof(Node));
			p->value=number;
			p->next=NULL;
			//Find the last
			Node *last=head;
			if(last){
				while(last->next){
					last=last->next;
				}
				//attach
				last->next=p;
			} else{
				head=p;
			}
		}
	}while(number!=-1);
  return 0;
}

链表的特点:

        使用链表结构可以克服 数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了 数组随机读取的优点,同时链表由于增加了结点的 指针域,空间开销比较大。链表最明显的好处就是,常规 数组排列关联项目的方式可能不同于这些数据项目在 记忆体磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的 节点,但是不允许 随机存取。链表有很多种不同的类型: 单向链表双向链表以及 循环链表


参考资料:

1、http://www.icourse163.org/learn/ZJU-200001?tid=1002775002#/learn/content?type=detail&id=1004060199&cid=1004981583&replay=true

2、https://baike.baidu.com/item/%E9%93%BE%E8%A1%A8/9794473?fr=aladdin

3、http://www.baike.com/wiki/%E9%93%BE%E8%A1%A8

4、https://en.wikipedia.org/wiki/Linked_list

猜你喜欢

转载自blog.csdn.net/zhanshen112/article/details/80720251