链式存储:用一组任意的存储单元存储线性表中的数据元素。用这种方法存储的线性表简称线性链表。
存储链表中结点的一组任意的存储单元可以是连续的,也可以是不连续的,甚至是零散的分布在内存中的任意位置上的。
链表中结点的逻辑顺序和物理顺序不一定相同。
非循环单链表
链表
链表的种类很多,有单链表、双链表、循环链表、非循环链表;在此,我们以非循环单链表为例,来讲链表的创建、求长度、排序、插入和排序。
链表包含以下特征:(1).由n个结点离散分配;(2).每个结点通过指针连接(3)每一个结点由一个前驱结点和一个后驱结点(4).首结点没有前驱结点,尾结点没有后驱结点;
满足上面的4条,我们就称为链表;链表既然由很多个结点,那结点又由什么组成?结点由两个部分组成,一是数据域,用来存放有效数据;二是指针域,用来指向下一个结点;下面用C语言来构建链表数据结构,首先应该构造出结点,然后再把所有的结点连起来,就构成了链表;
结点的构造
typedef struct Node
{
int data;//数据域,用来存放数据域;
struct Node *pNext;//定义一个结构体指针,指向下一次个与当前结点数据类型相同的结点
}NODE,*PNODE;
//NODE等价于 struct Node; PNODE等价于struct Node *; 此处用大写是为了与变量区分,可以让人容易变出是个数据类型
typedef 只是给数据类型取个别名,即 typedef 数据类型 别名;我们知道struct Node 是我们定义的数据类型;
结点是通过动态分配和释放来实现的,即需要时分配,不需要时释放。实现分别用malloc()和free()函数。
链表的创建
在创建链表之前,我们需要需要了解一下专业术语:
首结点:存放第一个有效数据的结点;
尾结点:存放最后一个有效数据的结点;
头结点:头结点的数据类型与首结点的数据类型相同,并且头结点是首结点前面的那个结点,并不存放有效数据;头结点的存在只是为了方便链表的操作。
头指针:指向头结点的指针;
尾指针:指向尾结点的指针;
头插法
首先,我们应该创建一个头结点,并用头指针指向它,用C语言描述:用malloc向计算机申请一块内存,并定义一个指向与头结点数据类型相同的指针(一定要判断申请内存是否成功);
然后,要知道要创建链表的长度,用一个循环来每次创建一个结点,并把每个结点连在一起;
创建头结点,定义头指针
head = (LNODE*)malloc(sizeof(LNODE)); //创建头结点
head->next = NULL;
循环创建结点
p = (LNODE*)malloc(sizeof(LNODE));
p->data = val;
p->next = head->next;
head->next = p; //钩链,新创建的结点总是作为首结点
尾插法
创建头结点,定义头指针、尾指针
head = (LNODE*)malloc(sizeof(LNODE));
head->next = NULL;
tail = head;
循环创建结点
p = (LNODE*)malloc(sizeof(LNODE));
p->data = val;
p->next = tail->next;
tail->next = p;
tail = p; //钩链,新创建的结点总是作为尾结点
对于单链表,无论是哪种操作,只要涉及到钩链(或重新钩链),如果没有明确给出直接后继,钩链(或重新钩链)的次序必须是“先右后左”。
向链表中插入结点
假如要在结点2的前面插入结点p,我们首先要找到结点2的前驱结点1,假设现在q指针指向结点1,则
p->next = q->next;
q->next = p;
删除链表中的结点
按序号删除
假如要删除结点2,只需要把结点1指针域指针指向结点3,但不要忘记释放结点2所占的内存,否则将会造成内存泄漏;首先必须找到结点2的前驱结点1,假设p指向结点1。
q=p->pNext; //首先用q保存要删除结点的地址;
p->pNext=q->pNext; //q->pNext=p->pNext->pNext; 修改指针使结点1指向结点3;
free(q); //释放结点2所占的内存;
按元素值删除
假如要删除结点2,使用元素值进行遍历,找到对应的结点,并且同时标记它的前一个结点。然后把结点1指针域指针指向结点3,释放结点2。
while((q != NULL) && (q->data != key))
p = q;
q = q->next;
if(q->data == key)
p->next = q->next;
free(q);
查找链表中的结点
按序号查找
获取链表中第i个结点
假如要获取结点2,首先必须找到结点2的前驱结点1,假设p指向结点1。
q = p->next; //定位到结点2
data = q->data; //取出结点2中数据元素
按元素值查找
获取链表中某个值对应结点
假如要获取结点2,直接使用元素值进行遍历,直到定位到对应的结点,并返回该结点。
while((p != NULL) && (p->data != key))
p = p->next;
if(p->data == key)
return p;
链表元素的排序
快速排序和冒泡排序的思想对于链表这个数据结构同样适用。
for(i=0, p=head->next; i<len-1; i++, p=p->next)
for(j=i+1, q=p->next; j<len; j++, q=q->next)
if(p->data > q->data)
temp = p->data;
p->data = q->data;
q->data = temp;
实例:
#include <stdio.h>
#include <stdlib.h>
#define false 0
#define ture 1
typedef int bool;
typedef struct Lnode
{
int data; //数据域,保存结点的值
struct LNODE *next; //指针域
}LNODE;
//头插法创建单链表,链表的头结点head作为返回值
LNODE *create_HeadLinkList(void)
{
int val, i, len;
LNODE *head, *p;
head = (LNODE*)malloc(sizeof(LNODE)); //创建头结点
if(NULL == head)
{
printf("Memory allocation failure!\n");
exit(-1);
}
head->next = NULL;
printf("please input the length of list: ");
scanf("%d", &len);
for(i=0; i<len; i++)
{
p = (LNODE*)malloc(sizeof(LNODE));
if(NULL == p)
{
printf("Memory allocation failure!\n");
exit(-1);
}
printf("please input the value of node: ");
scanf("%d", &val);
p->data = val;
p->next = head->next;
head->next = p; //钩链,新创建的结点总是作为首结点
}
return head;
}
//尾插法创建单链表,链表的头结点head作为返回值
LNODE *create_TailLinkList(void)
{
int val, i, len;
LNODE *head, *p, *tail;
head = (LNODE*)malloc(sizeof(LNODE));
if(NULL == head)
{
printf("Memory allocation failure!\n");
exit(-1);
}
tail = head;
head->next = NULL;
printf("please input the length of list: ");
scanf("%d", &len);
for(i=0; i<len; i++)
{
p = (LNODE*)malloc(sizeof(LNODE));
if(NULL == p)
{
printf("Memory allocation failure!\n");
exit(-1);
}
printf("please input the value of node: ");
scanf("%d", &val);
p->data = val;
p->next = tail->next;
tail->next = p;
tail = p; //钩链,新创建的结点总是作为尾结点
}
return head;
}
//按序号查找
int get_pos_data(LNODE *head, int pos) //链表中第pos个有效元素
{
int data;
int i = 0;
LNODE *p, *q;
p = head; //p指向头结点
while((NULL != p) && (i < pos-1))
{
p = p->next;
i++;
}
if(i != pos-1)
{
printf("list has empty node\n");
exit(-1);
}
q = p->next;
data = q->data;
return data;
}
//按值查找
LNODE *Locate_Node(LNODE *head, int key)
{
LNODE *p = head->next;
while((p != NULL) && (p->data != key))
{
p = p->next;
}
if(p->data == key)
{
return p;
}
else
{
printf("所要查找的结点不存在!\n");
return NULL;
}
}
//按序号删除
void Delete_PosLinkList(LNODE *head, int pos)
{
int i=0;
LNODE *p, *q;
p = head;
while((p != NULL) && (i < pos-1))
{
p = p->next;
i++;
}
if((p == NULL) || (i > pos-1))
{
printf("链表为空或者输入pos不错误\n");
exit(-1);
}
q = p->next;
p->next = q->next;
free(q); //释放q指向结点的内存
q = NULL; //千万不要忘记,否则会出现野指针
}
//按值删除
void Delete_KeyLinkList(LNODE *head, int key)
{
LNODE *p = head;
LNODE *q = head->next;
while((q != NULL) && (q->data != key))
{
p = q;
q = q->next;
}
if(q->data == key)
{
p->next = q->next;
free(q);
q = NULL;
}
else
{
printf("所要删除的结点不存在\n");
}
}
bool Insert_LinkList(LNODE *head, int pos, int val)
{
int i=0;
LNODE *p = head;
while((p != NULL) && (i < pos-1))
{
p = p->next;
i++;
}
if((p == NULL) && (i > pos-1))
{
printf("链表为空或者输入pos不错误\n");
return false;
}
LNODE *q = (LNODE*)malloc(sizeof(LNODE));
q->data = val;
q->next = p->next;
p->next = q;
}
void print_LinkList(LNODE *head)
{
LNODE *p = head->next;
while(p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
int Length_LinkList(LNODE *head)
{
int len=0;
LNODE *p = head->next;
while(p != NULL)
{
len++;
p = p->next;
}
return len;
}
void Sort_LinkList(LNODE *head)
{
int i, j, temp;
int len = Length_LinkList(head);
LNODE *p, *q;
for(i=0, p=head->next; i<len-1; i++, p=p->next)
{
for(j=i+1, q=p->next; j<len; j++, q=q->next)
{
if(p->data > q->data)
{
temp = p->data;
p->data = q->data;
q->data = temp;
}
}
}
}
int main()
{
int val;
LNODE *p;
//LNODE *list = create_HeadLinkList();
LNODE *list = create_TailLinkList();
print_LinkList(list);
val = get_pos_data(list, 3);
printf("val:%d\n", val);
p = Locate_Node(list, 4);
val = p->data;
printf("val:%d\n", val);
Delete_KeyLinkList(list, 2);
Delete_PosLinkList(list, 3);
print_LinkList(list);
Insert_LinkList(list, 3, 9);
print_LinkList(list);
Sort_LinkList(list);
print_LinkList(list);
return 0;
}
输出
please input the length of list: 5
please input the value of node: 1
please input the value of node: 2
please input the value of node: 3
please input the value of node: 4
please input the value of node: 5
1 2 3 4 5
val:3
val:4
1 3 5
1 3 9 5
1 3 5 9
循环链表
循环链表(Circular Linked List):是一种头尾相连接的链表。其特点是最后一个指针域指向链表的头结点,整个链表的指针域链接成一个环。
链表的创建
如果传入的为空链表,创建头结点并使头尾连接
p = (struct Node*)malloc(sizeof(struct Node));
p->next = p; //头尾相连
如果传入的为非空链表,遍历找到尾结点,插入新结点,使尾结点指向新结点,新结点指向头结点
for(Target=p; Target->next != p; Target=Target->next);
q = (struct Node*)malloc(sizeof(struct Node));
q->next = p; //新结点指向头结点
Target->next = q; //尾结点指向新结点
删除链表中的结点
如果要删除的结点为头结点,遍历找到尾结点,调整头结点的下一个结点为头结点,尾结点指向新的头结点,释放原来的头结点。
for(Target=p; Target->next != p; Target=Target->next);
q = p; //标记要删除的结点
p = p->next; //调整第2个结点为头结点
Target->next = p; //尾结点指向头结点
free(q);
如果删除的结点为普通结点,遍历链表,比较删除的位置是否超出了链表长度,如果超出了链表长度。找到尾节点的前一个结点,并将尾节点前一个结点的指针指向头结点,释放删除结点。如果没有超出链表长度,定位到要删除结点的前一个结点,并将要删除结点的前一个结点的指针指向要删除结点的后一个结点,释放要删除结点。
for(i=1, Target=p; (i!=pos-1)&&(Target->next != p); i++, Target=Target->next);
超出链表
if(Target->next == p)
for(Target=p; Target->next->next != p; Target=Target->next);
q = Target->next; //标记尾结点
Target->next = p;
free(q);
没有超出链表
q = Target->next; //标记要删除的结点
Target->next = q->next;
free(q);
向链表中插入结点
如果插入的位置为头结点的前面,遍历定位到尾节点,插入结点的指针指向头结点,尾节点的指针指向新插入的结点。
q = (struct Node*)malloc(sizeof(struct Node));
for(Target=p; Target->next != p; Target=Target->next);
q->next = p;
Target->next = q;
p = q;
如果插入的位置为普通位置,遍历定位到要插入结点的前一个结点,新插入的结点指针指向插入位置(pos)的结点,插入位置的前一个结点(pos-1)的指针指向新插入的结点。
for(i=1, Target=p; (i != pos-1)&&(Target->next != p); i++, Target=Target->next);
q = (struct Node*)malloc(sizeof(struct Node));
q->next = Target->next;
Target->next = q;
实例:
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *next;
};
struct Node *Create_list(struct Node *head)
{
int item;
struct Node *p = head;
struct Node *Target;
struct Node *q;
while(1)
{
if(p == NULL) //空链表,创建头结点并使头尾连接
{
p = (struct Node*)malloc(sizeof(struct Node));
if(p == NULL)
{
printf("创建失败!\n");
exit(0); //0正常退出,非0异常退出
}
p->next = p; //头尾相连
return p;
}
else
{ //使用循环找到尾结点,插入新结点,使尾结点指向新结点,新结点指向尾结点
for(Target=p; Target->next != p; Target=Target->next);
q = (struct Node*)malloc(sizeof(struct Node));
if(q == NULL)
{
printf("创建失败!\n");
exit(0);
}
printf("输入要插入的值!\n");
scanf("%d", &item);
q->data = item;
q->next = p; //新结点指向头结点
Target->next = q; //尾结点指向新结点
return p;
}
}
}
struct Node *Delete_Node(struct Node *head, int pos)
{
struct Node *Target, *q;
struct Node *p = head;
int i;
if(p == NULL)
{
printf("这是一个空链表!\n");
exit(0);
}
if(pos == 1) //删除头结点
{ //找到尾结点,为尾结点指向头结点铺垫
for(Target=p; Target->next != p; Target=Target->next);
q = p; //标记要删除的结点
p = p->next; //调整第2个结点为头结点
Target->next = p; //尾结点指向头结点
free(q);
return p;
}
else
{ //比较pos和链表的大小
for(i=1, Target=p; (i!=pos-1)&&(Target->next != p); i++, Target=Target->next);
if(Target->next == p) //删除的位置大于链表长度,即删除尾结点
{ //定位到尾结点的前一个结点
for(Target=p; Target->next->next != p; Target=Target->next);
q = Target->next; //标记尾结点
Target->next = p;
free(q);
return p;
}
else
{
q = Target->next; //标记要删除的结点
Target->next = q->next;
free(q);
return p;
}
}
}
struct Node *Insert_Node(struct Node *head, int pos)
{
struct Node *Target, *q;
struct Node *p = head;
int i, item;
printf("输入要插入的值!\n");
scanf("%d", &item);
if(pos == 1) //插入头结点
{
q = (struct Node*)malloc(sizeof(struct Node));
if(q == NULL)
{
printf("创建失败!\n");
exit(0);
} //定位到尾结点
for(Target=p; Target->next != p; Target=Target->next);
q->data = item;
q->next = p;
Target->next = q;
p = q;
return p;
}
else
{
for(i=1, Target=p; (i != pos-1)&&(Target->next != p); i++, Target=Target->next);
q = (struct Node*)malloc(sizeof(struct Node));
q->data = item;
q->next = Target->next;
Target->next = q;
return p;
}
}
int Find_Node(struct Node *head, int key)
{
struct Node *p = head->next;
int pos=2;
while((p->data != key) && (p->next != head))
{
pos++;
p = p->next;
}
return pos;
}
void Print_list(struct Node *head)
{
struct Node *p;
for(p=head->next; p!=head; p=p->next)
{
printf("%d ", p->data);
}
printf("\n");
}
/* int main()
{
struct Node *list = NULL;
int n, i;
while(n != 0)
{
printf("单循环链表的基本操作\n");
printf("-------------------\n");
printf("1.创建一个结点\n");
printf("2.插入一个结点\n");
printf("3.删除一个结点\n");
printf("4.显示链表成员\n");
printf("输入数字0,退出\n");
printf("请选择操作\n");
fflush(stdin);
scanf("%d", &n);
switch(n)
{
case 1:
{
list = Create_list(list);
break;
}
case 2:
{
printf("插入的位置是:\n");
scanf("%d", &i);
list = Insert_Node(list, i);
break;
}
case 3:
{
printf("删除的位置是:\n");
scanf("%d", &i);
list = Delete_Node(list, i);
break;
}
case 4:
{
Print_list(list);
break;
}
}
}
return 0;
} */
int main()
{
struct Node *list = NULL;
int i;
for(i=0; i<5; i++)
{
list = Create_list(list);
}
Print_list(list);
list = Insert_Node(list, 3);
Print_list(list);
list = Delete_Node(list, 3);
Print_list(list);
printf("值为3的结点是:%d\n", Find_Node(list, 3));
return 0;
}
输出
输入要插入的值!
1
输入要插入的值!
2
输入要插入的值!
3
输入要插入的值!
4
1 2 3 4
输入要插入的值!
7
1 7 2 3 4
1 2 3 4
值为3的结点是:4