1. 单链表概念&设计
单链表是一种链式存取的数据结构,,链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。以“结点的序列”表示的线性表称作线性链表(单链表),单链表是链式存取的结构。
对于链表的每一个结点,我们使用结构体(struct)进行设计,其主要内容有:
其中,DATA数据元素,可以为你想要储存的任何数据格式,可以是数组,可以是int,甚至可以是结构体(这就是传说中的结构体套结构体)
NEXT为一个指针,其代表了一个可以指向的区域,通常是用来指向下一个结点,链表的尾部NEXT指向NULL(空),因为尾部没有任何可以指向的空间了
//线性表的单链表的存储结构
typedef struct Node {
ElemType data;
struct Node* next;
}Node;
typedef struct Node* LinkList; //定义LinkList
2. 初始化
同任何的结构,类型一样,链表也需要初始化操作,初始化是创建一个单链表的前置节点并向后逐步添加节点,一般来说,我们所谓的初始化单链表一般指的是申请结点的空间,同时对一个结点辅以空值(NULL),其代码可以表示为:
LinkedList listinit(){
Node *L;
L=(Node*)malloc(sizeof(Node)); //开辟空间
if(L==NULL){ //判断是否开辟空间失败,这一步很有必要
printf("申请空间失败");
//exit(0); //开辟空间失败可以考虑直接结束程序
}
L->next=NULL; //指针指向空
}
在这里我们有一个注意点,就是一定要记住判断是否开辟空间失败,虽然在很多试题中以及常用的环境提供的环境非常安全,几乎没有开辟失败的存在,但是也一定要养成判断是否开辟失败并且判断失败后执行代码,但在生产中由于未知的情况造成一旦空间开辟失败任然在继续执行代码,后果将不堪设想,因此养成这样的判断是很有必要的,在C++中可以使用try-catch这样的语句进行优化。
3. 创建单链表(头插入法)
在初始化之后,就可以着手开始创建单链表了,单链表的创建分为头插入法和尾插入法两种,两者并无本质上的不同,都是利用指针指向下一个结点元素的方式进行逐个创建,只不过使用头插入法最终得到的结果是逆序的。
如图,为头插法的创建过程:
该方法从一个空表开始,生成新结点,并将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。
void CreateListHead(LinkList* L, int n) {
LinkList p;
int i;
srand(time(0)); //初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; //先建立一个带头结点的单链表
for (i = 0; i < n; i++) {
p = (LinkList)malloc(sizeof(Node)); //生成新结点
p->data = rand() % 100 + 1; //随机生成100以内的数字
p->next = (*L)->next;
(*L)->next = p; //插入到表头
}
}
4. 创建单链表(尾插入法)
如图,为尾插入法的创建过程。
头插法建立单链表的算法虽然简单,但生成的链表中结点的次序和输入数据的顺序不一致。若希望两者次序一致,可采用尾插法。
该方法是将新结点逐个插入到当前链表的表尾上,为此必须增加一个尾指针 r, 使其始终指向当前链表的尾结点,否则就无法正确的表达链表。
void CreateListTail(LinkList* L, int n) {
LinkList p, r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L; //r为指向尾部的结点
for (i = 0; i < n; i++) {
p = (Node*)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
r->next = p; //将表尾终端结点的指针指向新结点
r = p; //将当前新结点定义为表尾终端结点
}
r->next = NULL; //表示当前链表结束
}
1. 遍历单链表(打印,修改)
便利的概念想必大家都不会陌生,即就是从链表的头开始,逐步向后进行每一个元素的访问,这就是遍历,对于遍历操作,我们可以衍生出很多常用的数据操作,比如说查询元素,修改元素,获取元素个数,打印整个链表数据等等。
进行遍历的思路极其简单,只需要建立一个指向链表L的结点,然后沿着链表L逐个向后搜索即可。
对于遍历操作,以下是代码实现:
1 2 3 4 5 6 7 8 9 |
|
对于元素修改操作,以下是代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
简单的遍历设计的函数只需要void无参即可,而当我们需要进行元素修等涉及到元素操作时,我们可以设计一个LinkedList类型的函数,使其返回一个修改后的新链表。
以上的操作均用到了遍历的思维,针对于遍历还有非常多的用法供自主设计,请参考后文配套的习题进行练习。
2. 插入操作
链表的增加结点操作主要分为查找到第i个位置,将该位置的next指针修改为指向我们新插入的结点,而新插入的结点next指针指向我们i+1个位置的结点。其操作方式可以设置一个前驱结点,利用循环找到第i个位置,再进行插入。
如图,在DATA1和DATA2数据结点之中插入一个NEW_DATA数据结点:
从原来的链表状态
到新的链表状态:
以下是代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
3. 删除操作
删除元素要建立一个前驱结点和一个当前结点,当找到了我们需要删除的数据时,直接使用前驱结点跳过要删除的结点指向要删除结点的后一个结点,再将原有的结点通过free函数释放掉。
参考如图
//单链表的删除,在链表中删除值为x的元素
LinkedList LinkedListDelete(LinkedList L,int x) {
Node *p,*pre; //pre为前驱结点,p为查找的结点。
p = L->next;
while(p->data != x) { //查找值为x的元素
pre = p;
p = p->next;
}
pre->next = p->next; //删除操作,将其前驱next指向其后继。
free(p);
return L;
}
#include <stdio.h>
#include <stdlib.h>
//定义结点类型
typedef struct Node {
int data; //数据类型,你可以把int型的data换成任意数据类型,包括结构体struct等复合类型
struct Node *next; //单链表的指针域
} Node,*LinkedList;
//单链表的初始化
LinkedList LinkedListInit() {
Node *L;
L = (Node *)malloc(sizeof(Node)); //申请结点空间
if(L==NULL){ //判断申请空间是否失败
exit(0); //如果失败则退出程序
}
L->next = NULL; //将next设置为NULL,初始长度为0的单链表
return L;
}
//单链表的建立1,头插法建立单链表
LinkedList LinkedListCreatH() {
Node *L;
L = (Node *)malloc(sizeof(Node)); //申请头结点空间
L->next = NULL; //初始化一个空链表
int x; //x为链表数据域中的数据
while(scanf("%d",&x) != EOF) {
Node *p;
p = (Node *)malloc(sizeof(Node)); //申请新的结点
p->data = x; //结点数据域赋值
p->next = L->next; //将结点插入到表头L-->|2|-->|1|-->NULL
L->next = p;
}
return L;
}
//单链表的建立2,尾插法建立单链表
LinkedList LinkedListCreatT() {
Node *L;
L = (Node *)malloc(sizeof(Node)); //申请头结点空间
L->next = NULL; //初始化一个空链表
Node *r;
r = L; //r始终指向终端结点,开始时指向头结点
int x; //x为链表数据域中的数据
while(scanf("%d",&x) != EOF) {
Node *p;
p = (Node *)malloc(sizeof(Node)); //申请新的结点
p->data = x; //结点数据域赋值
r->next = p; //将结点插入到表头L-->|1|-->|2|-->NULL
r = p;
}
r->next = NULL;
return L;
}
//单链表的插入,在链表的第i个位置插入x的元素
LinkedList LinkedListInsert(LinkedList L,int i,int x) {
Node *pre; //pre为前驱结点
pre = L;
int tempi = 0;
for (tempi = 1; tempi < i; tempi++) {
pre = pre->next; //查找第i个位置的前驱结点
}
Node *p; //插入的结点为p
p = (Node *)malloc(sizeof(Node));
p->data = x;
p->next = pre->next;
pre->next = p;
return L;
}
//单链表的删除,在链表中删除值为x的元素
LinkedList LinkedListDelete(LinkedList L,int x) {
Node *p,*pre; //pre为前驱结点,p为查找的结点。
p = L->next;
while(p->data != x) { //查找值为x的元素
pre = p;
p = p->next;
}
pre->next = p->next; //删除操作,将其前驱next指向其后继。
free(p);
return L;
}
//链表内容的修改,再链表中修改值为x的元素变为为k。
LinkedList LinkedListReplace(LinkedList L,int x,int k) {
Node *p=L->next;
int i=0;
while(p){
if(p->data==x){
p->data=k;
}
p=p->next;
}
return L;
}
//便利输出单链表
void printList(LinkedList L){
Node *p=L->next;
int i=0;
while(p){
printf("第%d个元素的值为:%d\n",++i,p->data);
p=p->next;
}
}
int main() {
//创建
LinkedList list;
printf("请输入单链表的数据:以EOF结尾\n");
list = LinkedListCreatT();
//list=LinkedListCreatT();
printList(list);
//插入
int i;
int x;
printf("请输入插入数据的位置:");
scanf("%d",&i);
printf("请输入插入数据的值:");
scanf("%d",&x);
LinkedListInsert(list,i,x);
printList(list);
//修改
printf("请输入修改的数据:");
scanf("%d",&i);
printf("请输入修改后的值:");
scanf("%d",&x);
LinkedListReplace(list,i,x);
printList(list);
//删除
printf("请输入要删除的元素的值:");
scanf("%d",&x);
LinkedListDelete(list,x);
printList(list);
return 0;
}
#include <stdio.h>
#define MAXSIZE 100 //存储空间初始分配
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int ElemType;
typedef int Status;
//线性表的单链表的存储结构
typedef struct Node {
ElemType data;
struct Node* next;
}Node;
typedef struct Node* LinkList; //定义LinkList
//单链表的整表创建
//头插法
void CreateListHead(LinkList* L, int n);
//尾插法
void CreateListTail(LinkList* L, int n);
Status GetElem(LinkList L, int i, ElemType* e); //用e返回L中第i个数据元素的值
Status ListInsert(LinkList* L, int i, ElemType e); //在L中第i个结点位置之前插入新的数据元素e,L的长度加1
Status ListDelete(LinkList* L, int i, ElemType* e); //删除L的第i个结点,并用e返回其值,L的长度减1
//单链表的整表删除
Status ClearList(LinkList* L);
void showList(LinkList L);
int main()
{
LinkList List;
//头插法创建链表
CreateListHead(&List, 10);
showList(&List);
//返回第5个元素的值
ElemType e;
if (GetElem(List, 5, &e)) {
printf("得到第五个元素的值:%d\n", e);
}
else {
printf("获取元素失败!\n");
}
//在第三个位置插入元素e
if (ListInsert(&List, 3, e)) {
printf("插入成功!\n");
showList(&List);
}
else {
printf("插入失败!\n");
}
//删除第四个元素的值
if (ListDelete(&List, 4, &e)) {
printf("成功删除%d!\n", e);
showList(&List);
}
else {
printf("删除失败!\n");
}
return 0;
}
Status GetElem(LinkList L, int i, ElemType* e) {
int j;
LinkList p; //声明一指针
p = L->next; //让p指向链表L的第一个结点
j = 1; //j为计数器
while (p && j < i) {
p = p->next;
j++;
}
if (!p || j > i) {
return ERROR; //第i个结点不存在
}
*e = p->data; //取第i个结点的数据
return OK;
}
Status ListInsert(LinkList* L, int i, ElemType e) {
int j;
LinkList p, s;
p = *L;
j = 1;
while (p && j < i) { //寻找第i-1个结点
p = p->next;
j++;
}
if (!p || j > i) {
return ERROR;
}
s = (LinkList)malloc(sizeof(Node)); //生成新的结点
s->data = e;
s->next = p->next; //将p的后继结点赋给s的后继
p->next = s; //将s赋给p的后继
return OK;
}
Status ListDelete(LinkList* L, int i, ElemType* e){
int j;
LinkList p, q;
p = *L;
j = 1;
while (p->next && j < i) {
p = p->next;
j++;
}
if (!(p->next) || j > i) {
return ERROR;
}
q = p->next;
p->next = q->next;
*e = q->data;
free(q);
return OK;
}
void CreateListHead(LinkList* L, int n) {
LinkList p;
int i;
srand(time(0)); //初始化随机数种子
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; //先建立一个带头结点的单链表
for (i = 0; i < n; i++) {
p = (LinkList)malloc(sizeof(Node)); //生成新结点
p->data = rand() % 100 + 1; //随机生成100以内的数字
p->next = (*L)->next;
(*L)->next = p; //插入到表头
}
}
void CreateListTail(LinkList* L, int n) {
LinkList p, r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L; //r为指向尾部的结点
for (i = 0; i < n; i++) {
p = (Node*)malloc(sizeof(Node));
p->data = rand() % 100 + 1;
r->next = p; //将表尾终端结点的指针指向新结点
r = p; //将当前新结点定义为表尾终端结点
}
r->next = NULL; //表示当前链表结束
}
Status ClearList(LinkList* L) {
LinkList p, q;
p = (*L)->next; //p指向第一个结点
while (p) {
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
void showList(LinkList *L) {
printf("目前链表内容为:");
LinkList p;
p = (*L)->next;
while (p) {
printf("%d", p->data);
if (p->next) {
printf("-->");
}
p = p->next;
}
printf("\n");
}
|