链表是一种数据结构,用于存放数据
存储数据的两种结构:
-
数组
优点:存取速度快
缺点:需要一个连续的很大的内存空间,插入和删除元素的效率很低
-
链表
优点:插入删除元素效率高,而且不需要一个连续的很大的内存
缺点:查找某个位置的元素效率低
链表的一些术语
- 首节点:存放第一个有效数据的节点
- 头结点:
- 头结点是首节点前面的那个节点
- 头结点的数据类型和首节点的类型是一样的
- 头结点并不存放有效数据
- 设置头结点的目的是为了方便对链表的操作
- 尾节点:存放最后一个有效数据的接地那
- 头指针:存放头结点地址的指针变量
一个简单的链表
#include <stdio.h>
struct Test
{
int data;
struct Test * next;
};
int main(void)
{
struct Test t1 = {11,NULL};
struct Test t2 = {22,NULL};
struct Test t3 = {33,NULL};
t1.next = &t2;
t2.next = &t3;
return 0;
}
-
struct Test * next
表示定义了一个指针,这个指针用于存放的是struct Test
类型变量的地址 -
语句
t1.next = &t2
和t2.next = &t3
让t1
、t2
、t3
这三个变量串成了一个链表。t1
里面的next
存放着t2
的地址,t2
里面的next
存放着t3
的地址。 -
通过
t1
就能继而访问到t2
和t3
,比如要打印t1
、t2
、t3
的值:printf("%d %d %d",t1.data,t1.next->data,t1.next->next->data);
-
链表最后一个节点
t3
里面的next
的值是NULL,标识着链表尾端。可用于遍历链表:void printList(struct Test *p) { while(p!=NULL) { printf("%d ",p->data); p = p->next; } printf("\n"); }
上面定义了一个函数用于输出整个链表,在主函数里调用这个函数的时候只需把t1的地址当作参数传进去即可:
printList(&t1);
把
t1
的地址传进函数里后,p
就指向了t1
,t1
不为NULL
,输出t1
存储的值;然后p
指向t1
的next
,也就是t2
,t2
也不为NULL
,因此继续输出t2
存储的值;继续使p
指向t2的next
,也就是t3
,t3
不为NULL
,输出其存储的值后,使p
指向t3
的next
,这时p
的值为NULL
,循环即结束
在指定节点后面插入新节点
插入节点的时候要注意顺序:
- 先把新节点接在目标节点后面的那个节点
- 再把目标节点接向新节点
下面的代码演示了在链表t1
->t2
->t3
中,根据存储的值找到t2
节点,并在t2
后面插入一个节点,使链表变成t1
->t2
->tNew
->t3
:
#include <stdio.h>
struct Test
{
int data;
struct Test * next;
};
void insertNode(struct Test *p, int value, struct Test * new)
{
while(p!=NULL)
{
if(p->data == value)
{
new->next = p->next;
p->next = new;
return;
}
p = p->next;
}
return;
}
void printList(struct Test *p)
{
while(p!=NULL)
{
printf("%d ",p->data);
p = p->next;
}
putchar('\n');
}
int main(void)
{
struct Test t1 = {11,NULL};
struct Test t2 = {22,NULL};
struct Test t3 = {33,NULL};
t1.next = &t2;
t2.next = &t3;
printList(&t1);//输出结果:11 22 33
struct Test tNew = {123,NULL};
insertNode(&t1,22,&tNew);
printList(&t1);//输出结果:11 22 123 33
return 0;
}
在指定节点前面插入新节点
要考虑两种情况:
- 指定节点就是头结点。此时可以直接把新节点连到头结点即可
- 指定节点不是头结点。
下面代码演示了两种情况:
- 在链表
t1
->t2
->t3
中,把tNew1
节点插在t1
节点前面 - 在链表
tNew1
->t1
->t2
->t3
中,把tNew2
节点插在t3
节点前面
#include <stdio.h>
struct Test
{
int data;
struct Test * next;
};
void printList(struct Test * p)
{
while(p != NULL)
{
printf("%d ",p->data);
p = p->next;
}
printf("\n");
}
struct Test * insertNode(struct Test * p, int data, struct Test * new)
{
struct Test *head = p;
if(p->data == data)//如果是插在头节点前
{
new->next = p;
return new;
}
while(p->next != NULL)
{
if(p->next->data == data)
{
new->next = p->next;
p->next = new;
return head;
}
p = p->next;
}
return head;
}
int main(void)
{
struct Test t1 = {11,NULL};
struct Test t2 = {22,NULL};
struct Test t3 = {33,NULL};
t1.next = &t2;
t2.next = &t3;
struct Test * head = &t1;
printList(head);//输出结果:11 22 33
struct Test tNew1 = {101,NULL};
head = insertNode(head, 11, &tNew1);
printList(head);//输出结果:101 11 22 33
struct Test tNew2 = {123,NULL};
head = insertNode(head ,33, &tNew2);
printList(head);//输出结果:101 11 22 123 33
return 0;
}
删除一个节点
下面代码演示了在链表t1
->t2
->t3
中,删除t3
节点
#include <stdio.h>
struct Test
{
int data;
struct Test * next;
};
void printList(struct Test *p)
{
while(p != NULL)
{
printf("%d ",p->data);
p = p->next;
}
printf("\n");
}
struct Test * deleteNode(struct Test *p, int value)
{
struct Test * head = p;
if(p->data == value)
{
p = p->next;
//free(head);//error
return p;
}
while(p->next != NULL)
{
if(p->next->data == value)
{
p->next = p->next->next;
return head;
}
p = p->next;
}
}
int main(void)
{
struct Test t1 = {11,NULL};
struct Test t2 = {22,NULL};
struct Test t3 = {33,NULL};
t1.next = &t2;
t2.next = &t3;
struct Test * head = &t1;
printList(head);//输出结果:11 22 33
head = deleteNode(head,33);
printList(head);//输出结果:11 22
return 0;
}
删除节点后需要用free()
释放空间,但是在上面的代码中,t1
、t2
、t3
都属于静态内存,不能用free()
释放掉。如果是使用malloc()
来创建的节点,则可以使用free()
释放掉
头插法
创建链表时,新节点插在链表头
下面代码演示了使用malloc()创建链表,输入0则停止创建链表
#include <stdio.h>
#include <stdlib.h>
struct Test
{
int data;
struct Test * next;
};
struct Test * insertFromHead(struct Test *head, struct Test *new)//插在链表头前
{
new->next = head;
return new;
}
struct Test * creatList(struct Test *head)//创建链表
{
struct Test * new = NULL;
while(1)
{
new = (struct Test *)malloc(sizeof(struct Test));
printf("input the data:");
scanf("%d",&(new->data));
if(new->data == 0)//输入0则停止创建链表
{
printf("Creation End\n");
free(new);
return head;
}
head = insertFromHead(head, new);
}
return head;
}
void printList(struct Test * head)
{
while(head != NULL)
{
printf("%d ",head->data);
head = head->next;
}
printf("\n");
}
int main(void)
{
struct Test * head = NULL;
head = creatList(head);
printList(head);
struct Test tNew = {123,NULL};
head = insertFromHead(head,&tNew);
printList(head);
return 0;
}
insertFromHead()
用于把节点插在链表头,creatList()
用于创建链表。把两个功能分开是因为可以直接使用insertFromHead()
插入其它节点
尾插法
创建链表时,新节点插在链表尾
下面代码演示了使用尾插法来创建一个链表,输入0的时候则停止创建
#include <stdio.h>
#include <stdlib.h>
struct Test
{
int data;
struct Test * next;
};
void printList(struct Test *head)
{
while(head != NULL)
{
printf("%d ",head->data);
head = head->next;
}
printf("\n");
}
struct Test * insertFromTail(struct Test * head, struct Test * new)
{
if(head == NULL)
return new;
struct Test *p = head;
while(p->next != NULL)
p = p->next;
p->next = new;
return head;
}
struct Test * creatList(struct Test * head)
{
struct Test * new = NULL;
while(1)
{
new = (struct Test *)malloc(sizeof(struct Test));
printf("please input the data:");
scanf("%d",&(new->data));
if(new->data == 0)
{
printf("creation end\n");
free(new);
return head;
}
head = insertFromTail(head,new);
}
}
int main(void)
{
struct Test * head = NULL;
head = creatList(head);
printList(head);
return 0;
}
insertFromTail()
用于把节点插在链表尾,creatList()
用于创建链表
insertFromTail(struct Test * head, struct Test * new)
有两个参数,第一个是链表的头,第二个是要插进来的新节点。对头结点进行非空判断(if(head == NULL)
)这一步不可省略,因为如果传进来的头结点是空的话,在语句while(p->next != NULL)
中会出现错误,因为p
如果是NULL
就不能用p->next
这样的写法
一个完整的链表小例子
下面代码演示了由用户自定义链表长度,并输入要存储的数据:
# include <stdio.h>
# include <stdlib.h>
struct Node
{
int data; //数据域
struct Node * pNext; //指针域
};
struct Node * create_list(void)//用于构造链表
{
int len; //用来存放有效节点的个数
int i;
int val; //用来临时存放用户输入的结点的值
//构造一个不存放有效数据的头结点
struct Node * pHead = (struct Node *)malloc(sizeof(struct Node));
if (pHead==NULL)
{
printf("分配失败, 程序终止!\n");
exit(-1);
}
struct Node * pTail = pHead;
pTail->pNext = NULL;
//构造链表
printf("请输入您需要生成的链表节点的个数: len = ");
scanf("%d", &len);
for (i=0; i<len; ++i)
{
printf("请输入第%d个节点的值: ", i+1);
scanf("%d", &val);
struct Node * pNew = (struct Node *)malloc(sizeof(struct Node));
if (pNew==NULL)
{
printf("分配失败, 程序终止!\n");
exit(-1); //终止程序
}
pNew->data = val;
pTail->pNext = pNew;
pNew->pNext = NULL;
pTail = pNew;
}
return pHead;
}
void traverse_list(struct Node * pHead)//用于遍历链表
{
struct Node * p = pHead->pNext;
while (p != NULL)
{
printf("%d ", p->data);
p = p->pNext;
}
printf("\n");
return;
}
int main(void)
{
struct Node * pHead = NULL;
pHead = create_list();
traverse_list(pHead);
return 0;
}
要注意一点的是,在函数traverse_list(struct Node * pHead)
中,p = p->pNext
不能写成p++
,因为链表在内存中不一定是连续存放的,不能确保下一块内存空间是否就是下一个节点的内存空间