1.单链表的整表创建
单链表整表创建的算法思路:
1.声明一指针p和计数器变量i;
2.初始化一空链表L;
3.让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
4.循环:
生成一新结点赋值给p;
随机生成一数字赋值给p的数据域p->data;
将p插入到头结点与前一新结点之间。
实现代码算法如下:
/* 随机产生n个元素的值,建立带表头结点的单链
线性表L(头插法) */
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));
/* 随机生成100以内的数字 */
p->data = rand() % 100 + 1;
p->next = (*L)->next;
/* 插入到表头 */
(*L)->next = p;
}
}
这段算法代码里,我们其实用的是插队的办法,就是始终让新结点在第一的位置。我也可以把这种算法简称为头插法。
2.单链表的整表删除
当我们不打算使用这个单链表时,我们需要把它销毁,其实也就是在内存中将它释放掉,以便于留出空间给其他程序或软件使用。
单链表整表删除的算法思路如下:
1.声明一指针p和q;
2.将第一个结点赋值给p;
3.循环:
将下一结点赋值给q;
释放p;
将q赋值给p。
实现代码算法如下:
/* 初始条件:顺序线性表L已存在,操作结果:将L
重置为空表 */
Status ClearList(LinkList *L)
{
LinkList p, q;
/* p指向第一个结点 */
p = (*L)->next;
/* 没到表尾 */
while (p)
{
q = p->next;
free(p);
p=q;
}
/* 头结点指针域为空 */
(*L)->next = NULL;
return OK;
}
这段算法代码里,常见的错误就是有同学会觉得q变量没有存在的必要。在循环体内直接写free(p); p = p->next;即可。可这样会带来什么问题?
要知道p指向一个结点,它除了有数据域,还有指针域。你在做free(p);时,其实是在对它整个结点进行删除和内存释放的工作。这就好比皇帝快要病死了,却还没有册封太子,他儿子五六个,你说要是你脚一蹬倒是解脱了,这国家咋办,你那几个儿子咋办?这要是为了皇位,什么亲兄弟血肉情都成了浮云,一定会打起来。所以不行,皇帝不能马上死,得先把遗嘱写好,说清楚,哪个儿子做太子才行。而这个遗嘱就是变量q的作用,它使得下一个结点是谁得到了记录,以便于等当前结点释放后,把下一结点拿回来补充。
3.单链表结构与顺序存储结构优缺点
1.若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁插入和删除时,宜采用单链表结构。比如说游戏开发中,对于用户注册的个人信息,出了注册时插入数据外,绝大多数情况都是读取,所以应该考虑用顺序存储结构。而游戏中的玩家的武器或者装备列表,随着玩家的游戏过程中,可能会随时增加或删除,此时再用顺序存储就不太合适了,单链表结构就可以大展拳脚。当然,这只是简单的类比,现实中的软件开发,要考虑的问题会复杂很多。
2.当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构,这样可以不需要考虑存储空间的大小问题。而如果事先知道线性表的大致长度,比如一年12月,一周就是星期一至星期日共七天,这种用顺序结构效率会高很多。
4.静态链表的插入操作
/* 在L中第i个元素之前插入新的数据元素e */
Status ListInsert(StaticLinkList L, int i, ElemType e)
{
int j, k, l;
/* 注意k首先是最后一个元素的下标 */
k = MAX_SIZE - 1;
if (i < 1 || i > ListLength(L) + 1)
return ERROR;
/* 获得空闲分量的下标 */
j = Malloc_SSL(L);
if (j)
{
/* 将数据赋值给此分量的data */
L[j].data = e;
/* 找到第i个元素之前的位置 */
for (l = 1; l <= i - 1; l++)
k = L[k].cur;
/* 把第i个元素之前的cur赋值给新元素的cur */
L[j].cur = L[k].cur;
/* 把新元素的下标赋值给第i个元素之前元素的cur */
5.静态链表的删除操作
/* 在L中第i个元素之前插入新的数据元素e */
Status ListInsert(StaticLinkList L, int i, ElemType e)
{
int j, k, l;
k = MAXSIZE - 1; /* 注意k首先是最后一个元素的下标 */
if (i < 1 || i > ListLength(L) + 1)
return ERROR;
j = Malloc_SSL(L); /* 获得空闲分量的下标 */
if (j)
{
L[j].data = e; /* 将数据赋值给此分量的data */
for(l = 1; l <= i - 1; l++) /* 找到第i个元素之前的位置 */
k = L[k].cur;
L[j].cur = L[k].cur; /* 把第i个元素之前的cur赋值给新元素的cur */
L[k].cur = j; /* 把新元素的下标赋值给第i个元素之前元素的ur */
return OK;
}
return ERROR;
}
/* 删除在L中第i个数据元素 */
Status ListDelete(StaticLinkList L, int i)
{
int j, k;
if (i < 1 || i > ListLength(L))
return ERROR;
k = MAXSIZE - 1;
for (j = 1; j <= i - 1; j++)
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[j].cur;
Free_SSL(L, j);
return OK;
}
6.静态链表优缺点
优点:在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了顺序存储结构中的插入和删除操作需要移动大量元素 的缺点
缺点:没有解决连续存储分配带来的表长难以确定的问题;失去了顺序存储结构随机存取的特性
7.从尾到头输出链表
void PrintReversely(Node* pHead)
{
if (pHead)
{
if (pHead->next)
{
PrintReversely(pHead->next);
}
printf("%d",pHead->data);
}
}
8.从尾到头输出一个字符串
void ReversePrintStr(char str[])
{
if (*str != '/0')
{
if (*(str+1) != '/0')
{
ReversePrintStr(str+1);
}
printf("%c",*str);
}
}
9.定义一个函数求字符串的长度,要求该函数体内不能声明任何变量
int StrLen(char str[])
{
if(*str == '/0')
return 0;
return (StrLen(str+1) + 1);
}
10.链表结点的定义
struct Node
{
int data;
Node* next;
};