六、线性表的整表创建
声明一结点p和计数器变量i;
初始化一空链表L;
让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
循环实现后继结点的赋值和插入。
- 头插法
先让新节点的next指向头节点之后
然后让表头的next指向新节点
/* 头插法建立单链表示例 */
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;
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;
for( i=0; i < n; i++ )
{
p = (Node *)malloc(sizeof(Node));
p->data = rand()%100+1;
r->next = p;
r = p;
// 重点:
这句话的意思是r始终指向新插入的节点,p为临时指向的还没插入进来的节点,当插入进来后讲p传给r。(没传进来的时候p要传给r的next)
}
r->next = NULL;
}
七、单链表的整表删除
- 代码
单链表整表删除的算法思路如下:
声明结点p和q;
将第一个结点赋值给p,下一结点赋值给q;
循环执行释放p和将q赋值给p的操作;
/* 尾插法建立单链表演示 */
void CreateListTail(LinkList *L, int n)
{
LinkList p, r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L;
for( i=0; i < n; i++ )
{
p = (Node *)malloc(sizeof(Node));
p->data = rand()%100+1;
r->next = p;
r = p; // 备注:初学者可能很难理解这句,重点解释。
}
r->next = NULL;
}
- 存储分配方式
存储分配方式:
顺序存储结构用一段连续的存储单元依次存储线性表的数据元素。
单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素。 - 时间性能
- 查找
顺序存储结构O(1)
单链表O(n) - 插入和删除
顺序存储结构需要平均移动表长一半的元素,时间为O(n)
单链表在计算出某位置的指针后,插入和删除时间仅为O(1)
- 空间性能
顺序存储结构需要预分配存储空间,分大了,容易造成空间浪费,分小了,容易发生溢出。
单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制。
综上所述:
若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。
若需要频繁插入和删除时,宜采用单链表结构。
八、静态链表
用数组描述的链表叫做静态链表,这种描述方法叫做游标实现法。
image.png
- 线性表的静态链表存储结构
#define MAXSIZE 1000
typedef struct
{
ElemType data; // 数据
int cur; // 游标(Cursor)
} Component, StaticLinkList[MAXSIZE];
- 初始化
Status InitList(StaticLinkList space)
{
int i;
for( i=0; i < MAXSIZE-1; i++ )
space[i].cur = i + 1;
space[MAXSIZE-1].cur = 0;
return OK;
}
- 我们对数组的第一个和最后一个元素做特殊处理,他们的data不存放数据。
- 我们通常把未使用的数组元素称为备用链表。
- 数组的第一个元素,即下标为0的那个元素的cur就存放备用链表 (未使用的数组) 的第一个结点的下标。
- 数组的最后一个元素,即下标为MAXSIZE-1的cur则存放第一个有数值的元素的下标,相当于单链表中的头结点作用。
数组最后一个元素指向游标0
-
静态链表的插入操作
image.png
首先是获得空闲分量的下标:
int Malloc_SLL(StaticLinkList space)
{
int i = space[0].cur;
if( space[0].cur )
space[0].cur = space[i].cur;
// 把它的下一个分量用来作为备用。
return i;
}
插入代码
/* 在静态链表L中第i个元素之前插入新的数据元素e */
Status ListInsert( StaticLinkList L, int i, ElemType e )
{
int j, k, l;
k = MAX_SIZE - 1; // 数组的最后一个元素
if( i<1 || i>ListLength(L)+1 )
{
return ERROR;
}
j = Malloc_SLL(L);
if( j )
{
L[j].data = e;
for( l=1; l <= i-1; l++ )
{
k = L[k].cur;
}
L[j].cur = L[k].cur;
L[k].cur = j;
return OK;
}
return ERROR;
}
-
删除操作
image.png
/* 删除在L中的第i个数据元素 */
Status ListDelete(StaticLinkList L, int i)
{
int j, k;
if( i<1 || i>ListLength(L) )
{
return ERROR;
}
k = MAX_SIZE - 1;
for( j=1; j <= i-1; j++ )
{
k = L[k].cur; // k1 = 1, k2 = 5
}
j = L[k].cur; // j = 2
L[k].cur = L[j].cur;
Free_SLL(L, j);
return OK;
}
/* 将下标为k的空闲结点回收到备用链表 */
void Free_SLL(StaticLinkList space, int k)
{
space[k].cur = space[0].cur;
space[0].cur = k;
}
/* 返回L中数据元素个数 */
int ListLength(StaticLinkList L)
{
int j = 0;
int i = L[MAXSIZE-1].cur;
while(i)
{
i = L[i].cur;
j++;
}
return j;
}
优点:
在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点。
缺点:
没有解决连续存储分配(数组)带来的表长难以确定的问题。
失去了顺序存储结构随机存取的特性。
- 题目:快速找到未知长度单链表的中间节点。
普通的方法:首先遍历一遍单链表以确定单链表的长度L。然后再次从头节点出发循环L/2次找到单链表的中间节点。
算法复杂度为:O(L+L/2)=O(3L/2)。
设置两个指针search、mid都指向单链表的头节点。其中* search的移动速度是mid的2倍。当search指向末尾节点的时候,mid正好就在中间了。这也是标尺的思想。
- 快慢指针法:
Status GetMidNode(LinkList L, ElemType *e)
{
LinkList search, mid;
mid = search = L;
while (search->next != NULL)
{
//search移动的速度是 mid 的2倍
if (search->next->next != NULL)
{
search = search->next->next;
mid = mid->next;
}
else
{
search = search->next;
}
}
*e = mid->data;
return OK;
}
- 作业:写一个完整的程序,实现随机生成20个元素的链表(尾插法或头插法任意),用我们刚才学到的方法快速查找中间结点的值并显示。
九、循环链表
单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表成为单循环链表,简称循环链表。
image.png
- 并不是循环链表一定要有头结点
- 原来判断head->next是否为null,现在则是head->next是否等于head。
终端结点用尾指针rear指示,则查找终端结点是O(1),
而开始结点是rear->next->next,当然也是O(1)。
- 初始化部分:
/*初始化循环链表*/
void ds_init(node **pNode)
{
int item;
node *temp;
node *target;
printf("输入结点的值,输入0完成初始化\n");
while(1)
{
scanf("%d", &item);
fflush(stdin);
if(item == 0)
return;
if((*pNode) == NULL)
{ /*循环链表中只有一个结点*/
*pNode = (node*)malloc(sizeof(struct CLinkList));
if(!(*pNode))
exit(0);
(*pNode)->data = item;
(*pNode)->next = *pNode;
}
else
{
/*找到next指向第一个结点的结点*/
for(target = (*pNode); target->next != (*pNode); target = target->next);
/*生成一个新的结点*/
temp = (node *)malloc(sizeof(struct CLinkList));
if(!temp)
exit(0);
temp->data = item;
temp->next = *pNode;
target->next = temp;
}
}
}
- 插入部分:
/*链表存储结构的定义*/
typedef struct CLinkList
{
int data;
struct CLinkList *next;
}node;
/*插入结点*/
/*参数:链表的第一个结点,插入的位置*/
void ds_insert(node **pNode , int i)
{
node *temp;
node *target;
node *p;
int item;
int j = 1;
printf("输入要插入结点的值:");
scanf("%d", &item);
if(i == 1)
{ //新插入的结点作为第一个结点
temp = (node *)malloc(sizeof(struct CLinkList));
if(!temp)
exit(0);
temp->data = item;
/*寻找到最后一个结点*/
for(target = (*pNode); target->next != (*pNode); target = target->next);
temp->next = (*pNode);
target->next = temp;
*pNode = temp;
}
else
{
target = *pNode;
for( ; j < (i-1); ++j )
{
target = target->next;
}
// target指向第三个元素的
temp = (node *)malloc(sizeof(struct CLinkList));
if(!temp)
exit(0);
temp->data = item;
p = target->next;
target->next = temp;
temp->next = p;
}
}
- 删除部分:
/*删除结点*/
void ds_delete(node **pNode, int i)
{
node *target;
node *temp;
int j = 1;
if(i == 1)
{ //删除的是第一个结点
/*找到最后一个结点*/
for(target = *pNode; target->next != *pNode;target = target->next)
;
temp = *pNode;
*pNode = (*pNode)->next;
target->next = *pNode;
free(temp);
}
else
{
target = *pNode;
for( ; j < i-1; ++j)
{
target = target->next;
}
temp = target->next;
target->next = temp->next;
free(temp);
}
}
- 返回结点所在位置:
/*返回结点所在位置*/
int ds_search(node *pNode, int elem)
{
node *target;
int i = 1;
for(target = pNode; target->data != elem && target->next != pNode; ++i)
{
target = target->next;
}
if(target->next == pNode) /*表中不存在该元素*/
return 0;
else
return i;
}
网友评论