链表学习系列——1 基础概念之单向链表

公众号:

链表学习系列——1 基础概念之单向链表_第1张图片

声明:个人所写所有博客均为自己在学习中的记录与感想,或为在学习中总结他人学习成果,但因本人才疏学浅,如果大家在阅读过程中发现错误,欢迎大家指正。

 

学习LwIP网络协议过程中,内存方面用到了很多链表知识,下一博客打算写关于LwIP中的链表

 

一、关于顺序存储和链式存储

用数组存储数据元素,数组元素与数据元素之间的关系是顺序映射。这种存储可以概括为:数据对象以结点为单位,连续且等距的存放在一块存储区域。

(1)如果线性表的元素aij存储在Lij位置,那么a1,j+1存储在Li,j+1位置;

(2)如果对列中的第i个数据元素位于其循环存储的表示的位置Li,那么队列的第i+1个数据元素存储在(Li+1)%n的位置;

(3)如果栈顶结点位于LT,那么栈顶之下的节点位置是LT-1;

顺序存储机制缺失可以满足一些操作,如存取线性表中任意结点、入栈、出栈、入列,然而,基于顺序存储的其他操作,如在有序表中插入、删除,可能造成效率低。

 

采用链式表示可以完全克服效率等低下的缺陷,无论是向表中插入或从表中删除,操作过程都不需要移动数据。与顺序表示方法不同,链表表示的节点不再是连续且等距地存放在一块存储区域,相反,数据项可以存放在存储区的任何位置,存储区中数据元素的顺序无需保持表中元素的顺序,但存储区中的每个结点必须包括下一个结点的存储位置信息。因此,链式表示除存储每个结点的数据项之外,还要设置一个指针域或者链域,用来存放下一个结点的位置。采用链式表示的线性表称为链表,,一般而言,链表中的结点包含零个或多个数据项域,还要附加一个以上的指针域。

指针

C语言提供了对指针强大的支持。实际上,在C语言中,对于任意类型T,都有相应的指针类型,指针类型的取值是一个内存地址。指针类型使用的最重要的两个运算符是:

& 取地址运算符

* 复引用(或间址)运算符

使用指针能够获得更高程度的灵活性和效率。但是,指针也是不安全的。当用C语言进行程序设计时,把所有不指向实际对象的指针设为NULL是一种明智的做法。这样可以减少师徒访问程序范围以外的内存区域,或者没有合法指针指向的区域的可能性。有些机器允许复引用空指针,并且结果为NULL,使得程序得以继续执行。而在另一些机器上,无论0地址上的结果是什么,都产生一个严重错误。

另一个明智的程序设计技巧是在进行指针类型之间互相转换时,显式地使用强制类型转换(type cast)。例如:

 

pi = malloc(sizeof(int));  /* assign to pi a pointer to int */
pf = (float *)pi;  /* casts an int pointer to a float pointer */

 

 

 

另一个值得注意的方面是,在许多系统中,指针的大小和整形一样,因为整形是默认类型说明符,所以,有些程序员在定义函数时,常常省略返回类型,返回类型的默认类型为整形,而后可能当成指针来使用。在某些机器上是一种危险的做法,所以建议应该使用显示定义函数的返回类型。

 

二、单向链表

 

1、一个简单的单词链表:

首先定义链表的结点结构,声明各成员域类型,结构中应该含有一个字符数组,以及一个指向下一个结点的指针。

 

typedef struct listNode *listPointer;
struct listNode {
	char data[4];
	listPointer link;
};

 

 

 

以上程序包含自引用结构。应注意,指针listPointer定义出现在结构体struct listNode之前,C语言允许一个指针类型的定义出现在该类型定义之前。

结点结构声明之后,马上可以创建一个空表,语句是:

listPointer first = NULL;

就是说,创建的新表称为first。则first中存放的是这个表的起始存储地址。这个新表是空表,现在first的内容是0,表的初始化完成。NULL是C语言的保留字,其值就是0,用于条件判断语句。下面是判断空表的宏定义:

#define IS_EMPTY(first) (!(first))

 

2、两结点链表:

一个简单的两结点链表:

 

typedef  struct  listNode  *listPointer;
struct  listNode{
	int  data;
	listPointer  link;
};

 

 

创建一个两结点链表:

如何创建一个两结点链表呢?第一个结点的数据域是10,第二个结点的数据域是20.变量first指向第一个结点,second指向第二个结点。第一个结点的链域指向第二个结点,第二个结点的链域是NULL。并返回变量first,指向表头。

 

 

listPointer  create2()
{  /* create a linked list with two nodes */
	listPointer  first,  second;
	MALLOC(first, sizeof(*first));
	MALLOC(second, sizeof(*second));
	second->link = NULL;
	second->data = 20;
	first->data = 10;
	first->link = second;
	return first;
}

 

 

链表插入结点:

在链表中任意位置x之后插入一个结点,结点的数据域是50。函数的参量有两个,first指向表头,如果first为空指针,则返回他的值变成指向数据域为50的指针,由于这个指针的内容可能改变,必须传这个指针的地址,形参声明为listPointer *first。第二个参量值x不会改变,因此不用传它的地址。函数调用语句为 insert(&first, x) ,first是表头,x指向插入的位置。

 

void insert(listPointer *first, listPointer x)
{  /* insert a new node with data = 50 into the chain first after node x  */
	listPointer temp;
	MALLOC(temp, sizeof(*temp));
	temp->data = 50;
	if(*first) {
		temp->link = x->link;
		x->link = temp;
	} else {
		temp->link = NULL;
		*first = temp;
	}
}


insert函数在插入时要区分表是否为空。如果是空表,要先置temp为NULL,然后让first指向temp的地址。如果为非空表,则在x与其后继结点之间插入temp指向的结点。

 

 

删除链表结点:

在链表中删除给定结点比插入结点麻烦一些,删除结点要考虑结点所在位置。删除操作涉及三个指针。假定first指向表头,x指向待删结点,trail指向x的直接前驱结点。delete要把删除结点的存储空间返还给系统,所以调用了free。

 

void delete(listPointer *first, listPointer trail, listPointer x)
{  /* delete x from the list, trail is the preceding node and *first is the front of the list */
	if(trail)
		trail->link = x->link;
	else
		*first = (*first)->link;
	free(x);
}


打印链表:

 

 

void printList(listPointer first)
{
	printf("The list contains: ")
	for( ; first; first = first->link)
		printf("%4d", first->data);
	printf("\n");
}

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(数据结构,算法)