有时在写代码、阅读代码过程中,会感到自己的C语言基础知识不是很牢,因此会导致效率降低。所以,适时地再回过头来温习一下有关的基础知识会增进自己的理解,帮助是很大的。
在此,我自己写了一个程序,以便学习理解数据结构里很基本也很重要的一个部分——链表。当然,链表也可以细分为单链表、双向链表等。这里我写的是单链表。不仅复习了一下数据结构,还复习了一下C语言基础、指针,一举多得。
下面把代码分为:链表创建、表的遍历、链表插入一个新节点以及数据、链表删除一个节点、链表的销毁。这几个基础的操作,最后还有一个表的逆序输出。
===========================================
链表是一种物理存储单元上非连续、非顺序的链式存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。可以结合数组来进行区别,数组是一种线性结构,数组在内存中是连续存放的。
简单区别整理如下:
1、数组静态分配内存(固定长度),链表动态分配内存(长度不固定)。
2、数组在内存中连续存储数据,链表不连续。
3、数组数据在栈中,链表数据在堆中。
以图的形式给出一个单链表:
可以看到:首先要有一个头结点head。图中A、B、C、D分别指代有4个数据。他们在内存中的地址分别为1200、1360、1520、1199。
head保存的地址是指向链表中第一个节点,第一个节点又指向第二个节点……直到最后一个节点,即遇到某个地方为NULL,链表结束。因此,通过指针间接来访问某个变量,知道了这个指针即知道了这个变量存放的地方,而这个指针在上一个节点有保存下来了。所以一个一个节点通过指针“链接”了起来,形成了单链表。
由此我们可以获得:每个节点(除头节点外),包含了数据成员(实际的数据),以及指向下一个结构体类型节点的指针(即保存的下一个节点的地址)。
一个简单的list结构体如下:
typedef struct LINK_NODE //typedef一种数据类型定义一个新名字,回忆一下对比#define的区别
{
int data; //数据成员
struct LINK_NODE *next; //指向下一个节点指针
}LINK_NODE; //结构体类型的变量
一、list创建:
/* 创建一张单链表 */
LINK_NODE *listCreate(int cnt)
{
LINK_NODE *head; //头节点
LINK_NODE *ptr = NULL; //链接节点的指针
LINK_NODE *node = NULL;
int i;
head = (LINK_NODE *)malloc(sizeof(LINK_NODE)); //head头节点分配内存空间。
if(NULL == head)
{
printf("head 内存分配失败,链表初始化失败\n");
exit(0);
}
head->next = NULL;
ptr = head; //ptr指向了head,ptr便是用来将各个节点链接的指针
if(cnt <= 0)
{
printf("请输入大于0的整数.\n");
exit(0);
}
for(i=0; i<cnt; i++)
{
node = (LINK_NODE *)malloc(sizeof(LINK_NODE)); //创建新节点,新节点分配内存。
if(NULL == node)
{
printf("node 内存分配失败!\n");
exit(0);
}
else
{
printf("第[%d]个数据是:", i+1); //这里i+1是指在头节点后的那个节点,是1号节点。头节点是0号。
scanf("%d", &node->data); //输入数据
ptr->next = node; //将新产生的node的地址给保存到了ptr->next中,ptr就和数据节点node链接起来了
ptr = node; //再将node的地址给ptr,此时ptr就是保存了当前数据的地址
}
}
node->next = NULL; //最后一个数据节点指向NULL,结束单向链表。
return head; //返回头结点的地址
}
二、list遍历:
void displayList(LINK_NODE *head)
{
LINK_NODE *ptr = head;
if(NULL == ptr)
{
printf("链表为空!\n");
}
else
{
while(NULL != ptr)
{
printf("%6d", ptr->data); //把数据读出来
ptr = ptr->next; //并不断把ptr后移(也就是把next中保存的每次的新节点地址取得)
} //直到为NULL时,遍历结束
printf("\n");
}
}
三、list插入:
在某个指定的节点后插入
void insert(LINK_NODE *head, int pos, int data)
{
LINK_NODE *ptr = head;
LINK_NODE *node = NULL;
int i = 1;
while(ptr && i<pos) //遍历这个链表,通过传入的pos参数决定在哪一个节点后插入新节点
{
ptr = ptr->next;
++i;
}
node = (LINK_NODE *)malloc(sizeof(LINK_NODE)); //创建一个将要插入的新节点
node->data = data; //新节点的新数据
node->next = ptr->next; //将原来ptr->next保存的节点地址给到新节点node->next中
ptr->next = node; //将新节点地址给ptr->next。
//这样就把原来的“旧”链给断了,新增了一条“新”链。自行画个图可能好理解一些
}
四、list删除
void nodeDelete(LINK_NODE *head, int pos)
{
LINK_NODE *ptr = head;
LINK_NODE *node = NULL;
int i = 1;
while(ptr && i<pos) //依然先遍历,找到想删除节点的位置
{
ptr = ptr->next;
++i;
}
node = (LINK_NODE *)malloc(sizeof(LINK_NODE));
node = ptr->next;
ptr->next = node->next;
//可以看到刚好就是插入操作的逆操作,还是可以自行画图加以理解
free(node); //就是把某个节点free掉
}
五、list销毁
void listDestroy(LINK_NODE *head)
{
LINK_NODE *ptr = NULL;
while(head) //把包括头节点在内的所有节点都free掉
{
ptr = head;
head = head->next;
printf("Node freed :[%d]\n", ptr->data);
free(ptr);
}
}
六、链表逆序输出
LINK_NODE *link_revers(LINK_NODE *head)
{
LINK_NODE *next;
LINK_NODE *prev = NULL;
while(NULL != head)
{
next = head->next;
head->next = prev;
prev = head;
head = next;
}
//循环终止即head为NULL
return prev;
}
用图示来说明逆序:
主函数:
int main(int argc, char **argv)
{
LINK_NODE *plist = NULL;
int pos_insert, data, cnt;
int pos_del;
printf("输入节点个数(不包括头结点):");
scanf("%d", &cnt);
plist = listCreate(cnt);
printf("display this list :\n");
displayList(plist);
plist = link_revers(plist);
printf("逆序输出结果是:\n");
displayList(plist);
printf("输入要插入的节点的位置和数据(pos data):");
scanf("%d %d", &pos_insert ,&data);
if(pos_insert > cnt)
{
printf("超出list的范围!\n");
exit(0);
}
else
{
insert(plist, pos_insert, data);
printf("在第[%d]号节点后插入完毕的list:", pos_insert);
}
displayList(plist);
printf("输入你要删除的节点:");
scanf("%d", &pos_del);
if(pos_del > cnt)
{
printf("超出list的范围!\n");
exit(0);
}
else
{
nodeDelete(plist, pos_del);
printf("删除第[%d]号节点后的list:", pos_del);
}
displayList(plist);
listDestroy(plist);
return 0;
}
测试结果:
头节点是没有数据的,因此为0.
下标是从0开始的 . 0,1,2,3,4,……