单链表
链式存储的线性表又称之为单链表。他是通过一组任意的存储单元来存储线性表中的数据元素。为了建立起数据元素之间的线性关系,每个节点除了存放自身的数据信息外,还要一个指向其后继的指针。
利用单链表可以解决顺序表需要大量的连续存储空间的缺点,但是单链表附加指针域,也带来了浪费存储空间的缺点。由于单链表是离散的分布在存储空间中,所以单链表是非随机存取的。
通常为了操作方便,会给单链表的第一个之前附加一个节点,称之为头结点。头结点的数据域可以不设任何信息,但可以记录表长等信息。
引入头结点带来的两个有点:
- 由于开始节点的位置被存放在头结点中,所以链表第一个位置上的操作和在表其他位置上的操作一致,无需进行特殊处理;
- 无论链表是否为空,其头指针指向头结点的非空指针,因此空表和非空表的处理也就统一。带头单链表的判定空条件为L->next = =NULL
/************************************************************************************************
头指针:
1,头指针是指链表指向第一个节点的指针,若链表的有头结点,则是指向头结点的指针
2,头指针有标识作用,长用头指针冠以链表的名字
3,无论链表是否为空,头指针均不能为空,头指针是链表的必要元素
头结点:
1,头结点是为了操作的统一和方便而设立,放在第一个元素结点之前,其数据域一般无意义
2,有了头结点,对在第一个结点前插入和删除第一个元素结点,其操作和其他元素的操作便统一
3,头结点不一定是链表的必要元素
************************************************************************************************/
/**********************************单链表结构和顺序存储结构优缺点***************************************
存储分配方式:
1,顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
2,单链表采用链式的存储结构,用一组任意的存储单元存放线性表元素
时间性能:
1,查找: 顺序存储结构O(1) 单链表O(n)
2,插入删除: 顺序存储结构要平均移动一半的元素, O(n)
单链表在查出某位置的指针后,插入删除均为O(1)
空间性能:
1,顺序存储需要预先分配存储空间,分配大了浪费,小了容易发生上溢出
2,单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
************************************************************************************************/
#include <malloc.h>
#define MAXSIZE 20 //存储空间初始分配量
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int ElemType; //ElemType 数据类型
typedef int Status;
typedef struct
{
ElemType date;
struct Node *next;
}Node,*LinkList;
//单链表的读取 O(n)
/************************************************************************************************
1,声明一个节点p指向链表的第一个节点,初始化j从1开始;
2,当j<i时,就遍历链表,p向后移动,不断指向下一个节点,j++;
3,若到链表末尾p为空,则说明第i个元素不存在;
4,否则查找成,返回节点p的数据
************************************************************************************************/
Status GetElem(LinkList L, int i, ElemType *e)
{
int j;
Node *p; //声明一个结点
p = L->next; //让p指向链表的第一个节点
j = 1; //j为计数器
while(p && j<i) //p不为空并且计数器j没有等于i,循环继续
{
p = p->next; //移向下一结点
++j; //计数加一
}
if( !p || j>i) //第i个元素不存在
return ERROR;
*e = p->date; //取得第i个元素
return OK;
}
/************************************************************************************************
单链表的插入和删除算法,实际是由两部分组成:O(n)
1,遍历查找第i个元素
2,插入或者删除相应元素
************************************************************************************************/
//单链表的插入
/************************************************************************************************
1,声明结点p并指向链表第一个节点,初始化j从1开始;
2,当j<i,遍历链表,让p的指针向后移动,不断的指向下一节点,并且j++;
3,若到链表末尾,p为空,则说明i元素不存在;
4,否则查找成功,在系统中生成一个空节点s;
5,将元素e赋值给s->data;
6,单链表的插入标准语句 s->next = p->next; p->next = s;
7,返回成功
************************************************************************************************/
Status ListInsert(LinkList *L, int i, ElemType e)
{
int j;
Node * p,*s;
p = *L;
j =1;
while(p && j<i) //寻找第i个结点
{
p = p->next;
++j;
}
if(!p || j>i) //i节点不存在
return ERROR;
//s = (LinkList)malloc(sizeof(Node));
s= (Node*)malloc(sizeof(Node)); //生成新节点, #include<malloc.h>
s->date = e; //将值付给s->date
s->next = p->next; //将p的后继赋给s的后继
p->next = s; //将p的后继指向s
return OK;
}
// 若要在p之前插入S,可以在插入后,对调两个元素内的数据信息
s->next = p->next;
tmp = s->data;
s->data = p->next;
p->next = tmp;
//单链表删除
/************************************************************************************************
1,声明一个结点p指向链表的第一个节点,初始化j从1开始;
2,当j<i是,遍历整个链表,让p指针右移动,不断的指向向下的节点,++j;
3,若链表末尾为空,则说明i元素不存在
4,否则查找成功,将欲删除的节点p->next给q;
5,单链表标准删除语句,p->next = q->next;
6,将q节点的数据赋值给e,作为返回值;
7,释放q节点;
8,返回成功
************************************************************************************************/
Status ListDelet(LinkList *L, int i, ElemType *e)
{
int j;
Node *p,*q;
p = *L;
j = 1;
while(p->next && j<i) //遍历寻找第i个元素的前一元素,p->next为第i元素
{
p = p->next;
++j;
}
if(!(p->next) || j>i)
return ERROR; //元素不存在返回ERROR
q = p->next; //让q指向第i个元素
p->next = q->next; //p的后继指向q的后继
*e = q->date; //q的值赋给e,以便返回
free(q); //清除结点,释放内存
return OK;
}
// 单链表整表创建
//从“空表”的初始状态起,依次建立各个节点,并逐个加入链表中
/************************************************************************************************
1,声明一个节点p和计数器i;
2,初始化空链表L;
3,让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
4,循环创建链表 (头插法):
生成新的结点赋值给p;
生成数字赋值给p的数据域p->data;
将p插入到头结点与前一新节点之间。
************************************************************************************************/
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; //建立带头结点的单链表,并且头结点指针为空
for( i= 0; i<n; i++)
{
p = (LinkList)malloc(sizeof(Node)); //生成新结点
p->date = i;
p->next = (*L)->next; //结点插入到表头
(*L)->next = p;
}
}
//尾插法
void CreateListTail(LinkList *L, int n)
{
LinkList p,q;
int i;
*L = (LinkList)malloc(sizeof(Node));//创建线性表头
p = *L; //p指向线性表的尾部
for( i = 0; i<n; i++)
{
q = (LinkList)malloc(sizeof(Node)); //生成新的结点
q->date = i;
p->next = q; //将尾部节点的指针指向新的结点
p = q; //尾部节点指针后移
}
p->next = NULL; //链表结束
}
//单链表 整表删除
/************************************************************************************************
1,声明一个结点p和q
2,将第一个结点赋值给p;
3,循环
将下一结点赋值给q;
释放q;
将q赋值给p;
************************************************************************************************/
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;
}