您的位置:首页 > 其它

线性链式表

2016-01-18 12:05 253 查看

定义

线性链式表就是我们常说的链表,也被称为线性表的非顺序映像或链式映像。它与线性顺序表的最大区别就是各个存储单元是非连续的。它无法通过数学公式来确定每个存储单元在内存中的关系。链表中最重要的两个概念就是结点(Node)和next指针。在单向链表中结点通常只包含两个存储域,一个是data数据域,用来存储结点所代表的值。第二个是,next指针域,用来存储结点的后继。因此next指针成了寻找结点后继的唯一途径,也就是只有知道头结点,才能通过next指针一个个往下寻找。所以链表的结构是链式的,而非顺序的,他们在内存结构中往往是离散的。

分类

按照结点中指针域的个数分类有两种: 单向链表双向链表

单向链表就是一个结点只包含一个next指针域,即一个结点只知道它的后继结点是谁。遍历链表的时候,只能从前往后遍历。



双向链表就是一个结点包含pre指针域和next指针域,即一个结点即知道它的后继结点,也知道它的前驱结点。遍历链表的时候,可以从前往后,也可以从后往前。但是从后往前。从前往后需要head指针开始,从后往前需要tail指针开始。



两者区别: 结点的前驱和后继是否都知道,遍历的时候是否支持双向遍历。

按照存储方式分类有两种: 动态链表静态链表

动态链表就是平常我们最常见的链表,链表的插入,删除,建立都是动态执行的。没有内存空间的预申请以及浪费。通常结点的定义方式就是包含data和next指针的结构体。当需要新建结点插入的时候通常用malloc申请新的结点内存空间,进行初始化数据域,修改指针域插入链表。当需要删除结点的时候,往往先修改链表的指针域,然后free结点。因此动态链表的元素容量不是固定的,是根据需求变化的。



静态链表是给有些不支持指针的编程语言实现链表的一种方式,通常用数组来实现。数组的每个分量是一个复合类型数据(结构体或者说类Class)。每个分量都有两个存储域,一个是data数据域,第二个next数据域,是保存当前分量的后继结点位于数组中的第几个分量。通常静态量插入,删除,建立都需要一个备用空间链表来管理内存空间的复用。插入的时候,需要从备用空间链表中申请复用可以复用的结点。如果备用空间链表没有可以复用的结点,往往把数组第一个没有存储任何数据的分量作为下一个结点使用。删除的时候,要将删除的结点放入备用空间链表,以提供复用。静态链表的容量是预申请的,是固定的。

两者区别: 动态链表存储空间不是预申请,容量不固定。静态链表存储空间是预申请的,容量通常在申请数组大小的就确定下来了。



按照末尾结点指针域是否为头结点分类: 循环链表非循环链表

循环链表 即链表最后一个结点的next指针域为头结点,构成环状结构。通常遍历链表的结束状态谓词不能用结点!=NULL来确定,而是应该用结点的后继结点!=HEAD结点。



非循环链表 即链表最后一个结点的next指针域为NULL,不构成环状结构。遍历链表的结束状态谓词用结点!=NULL来确定。

两者区别: 末尾结点的next指针域是否指向头结点。

相关操作以及时间复杂度分析

索引(Index)

由于链表每个存储单元不是连续的,也没有任何数学公式可以表示,因此索引操作只能通过遍历来获取。遍历通常用next指针域来找到对应结点, 假设索引链表的第5个数据,通常要从头结点开始,经过
for(int i=0; i<5; i++) p = p->next
,到达第五个结点,然后返回对应的值(
return p->data;
)。现在假设i结点被索引的概率为pi。则索引操作的数学期望值为:



现在假设pi都具有相同的概率1/n; 那么对上述表达式进行化简得到如下结果:



再将如上级数进行整理可得



因此索引的时间复杂度为O(n)。

查找(find)

因为在链表中查找也是需要通过遍历的, 它在遍历的过程和索引操作是一样的,只不过循环的终止条件是是否查找到对应的值,而非需要走到位置的次数。现在假设需要寻找的值在结点i的概率为pi。则查找操作的数学期望值为:



现在假设pi都具有相同的概率1/n; 那么对上述表达式进行化简得到如下结果:



再将如上级数进行整理可得



因此查找的时间复杂度为O(n)。

插入(Insert)

与顺序表相比,链表的最大特点就在于插入与删除的方便。它不需要将插入位置之后的元素全部往后移动一个位置,只需要将插入结点的next指针域指向待插入位置之后的结点,将待插入位置之前的结点的next指针域指向插入结点就完成了插入。例如,以下链表 A->B->C中,将D插入B,C之间,只要进行如下操作:D->next = C, B->next = D, 就完成了插入,形成了A->B->D->C。

因此已经定位到插入位置的前提下链表插入的时间复杂度为O(1); 但是只给定在插入到第2个元素之前,还需要进行定位操作,定位操作就是索引操作的,因此时间复杂度是O(n)。

所以,如果没有定位到插入位置,链表插入操作的时间复杂度还是O(n)。

删除(Delete)

删除操作也是链表的一个优势,它不需要将删除位置以后的所有元素往前移动一个位置,只需要将删除位置之前的结点的next指针域指向删除位置之后的结点,然后free掉删除的结点即可。例如,以下链表 A->B->C中,将B删除,只要进行如下操作:A->next = C; free(B);就完成了删除,形成了A->C。

因此已经定位到删除位置的前提下链表删除的时间复杂度为O(1); 但是只给定在删除第2个元素,还需要进行定位操作,定位操作就是索引操作的,因此时间复杂度是O(n)。

所以,如果没有定位删除位置,链表删除操作的时间复杂度还是O(n)。

合并(Merge)

链表合并操作的算法和线性顺序表的合并操作的算法是一样的,时间复杂度也是一样的,都是O(m+n)。只不过区别于线性顺序表的合并,链表不需要申请存放合并之后线性表的空间,而是只要修改各个节点next指针域,使得通过结点“打散-重组”的方式进行合并,因此空间复杂度明显比线性表要低。相关的合并算法见我的另一篇博文《线性顺序表》

实现代码

动态单向链表

#include<stdio.h>
#include<stdlib.h>

typedef struct LNode *PNode;

typedef struct LNode{
int data;
PNode next;
}LNode, *PNode;

/*
* 从后往前建立链表
*/
void create_list(PNode *head, int n)
{
*head = (PNode) malloc(sizeof(LNode));
(*head)->data = n;
(*head)->next = NULL;
for(int i=n; i>0; i--){
PNode node = (PNode) malloc(sizeof(LNode));
scanf("%d", &node->data);
node->next = (*head)->next;
(*head)->next = node;
}
}

/*
* 在对应下标位置插入新的结点
*/
void insert(PNode head, int index, int newValue)
{
PNode p = head;

for(int i=0; i< index && p; i++)
p = p->next;
PNode newNode = (PNode)malloc(sizeof(LNode));
newNode->data = newValue;
newNode->next = p->next;
p->next = newNode;
head->data++;
}

/*
* 删除对应下标的链表结点
*/
int deleteElement(PNode head, int index)
{
PNode p = head;
for(int i=0; i < index && p; i++)
p = p->next;
PNode deleteNode = p->next;
p->next = deleteNode->next;
int deleteValue = deleteNode->data;
free(deleteNode);   // 释放结点内存
head->data--;
return deleteValue;
}

/*
* 获取对应下标元素值
*/
int getNodeValue(PNode head, int index)
{
PNode p = head;
for(int i=0; i <= index; i++)
p = p->next;
return p->data;
}

/*
* 合并链表
*/
void merge_list(PNode head1, PNode head2, PNode *rs)
{
PNode p1 = head1->next;
PNode p2 = head2->next;
*rs = (PNode) malloc(sizeof(LNode));
(*rs)->next = NULL;
(*rs)->data = head1->data + head2->data;
PNode prs = (*rs);

while(p1&&p2){
if(p1->data <= p2->data){
prs->next = p1;
p1 = p1->next;
prs = prs->next;
}else{
prs->next = p2;
p2 = p2->next;
prs = prs->next;
}
}

while(p1) {
prs->next = p1;
p1 = p1->next;
prs = prs->next;
}

while(p2) {
prs->next = p2;
p2 = p2->next;
prs = prs->next;
}

free(head1);
free(head2);
}

/*
* 打印链表信息,包含链表长度,元素内容
*/
void print_list_info(PNode head)
{
PNode p = head;
printf("The length of linked list is %d\nData: ", head->data);
while(p->next) {
p = p->next;
printf("%d ", p->data);
}
printf("\n");
}

int main()
{

PNode head;
create_list(&head, 10);
print_list_info(head);
insert(head, 0, 100);
print_list_info(head);

PNode head1;
PNode head2;
PNode rs;

create_list(&head1, 5);
create_list(&head2, 3);
merge_list(head1, head2, &rs);
print_list_info(rs);

return 0;
}


静态单向链表

#include<stdio.h>
#include<stdlib.h>

#define MAX_SIZE 100 // 链表的最大长度
#define END -1

typedef struct {
int data;
int next;
} SLNode, SLinkedList[MAX_SIZE];

void create_slist(SLinkedList slist, int n)
{
slist[0].data = n; // 头结点保存链表长度信息
slist[0].next = 1;

// 注意这里是从1开始的,不是0. 0用来作为链表的头结点了
for(int i=1; i<=n; i++){
scanf("%d", &slist[i].data);
slist[i].next = i+1;
}
slist
.next = END; // 链表最后一个结点的next为END(-1)
}

void init_backup_list(SLinkedList backupList)
{
backupList[0].data = 0;
backupList[0].next = END;
}

/*
* 从备用空间链表中重用结点空间
*/
int malloc_node(SLinkedList backupList)
{
int m_node = backupList[0].next; // 备用空间链表的第一个结点
if(m_node != END) backupList[0].next = backupList[m_node].next; // 如果m_node不为END,也就是说备用空间链表的第一个结点可以被重用,则删除第一个结点,提供重用
return m_node;
}

/*
* 将删除的结点移入备用空间链表中
*/
void free_node(SLinkedList backupList, int index)
{
backupList[index].next = backupList[0].next;
backupList[0].next = index;
}

void insert(SLinkedList slist, SLinkedList backupList, int index, int value)
{
int p = 0;
for(int i=0; i < index; i++){
p = slist[p].next;
}

// 先从备用链表中查看是否有可以重用的结点
int insert_p = malloc_node(backupList);
insert_p = insert_p == END ? slist[0].data+1 : insert_p; // 若insert_p为END说明没有重用的结点,则从链表空间中取下一个新的结点作为插入结点

// 插入结点
slist[insert_p].data = value;
slist[insert_p].next = slist[p].next;
slist[p].next = insert_p;

slist[0].data++;
}

void deleteElement(SLinkedList slist, SLinkedList backupList, int index)
{
int p = 0;
for(int i=0; i < index; i++){
p = slist[p].next;
}

slist[p].next = slist[slist[p].next].next;
free_node(backupList, index);
}

int getElement(SLinkedList slist, int index)
{
int p = 0;
for(int i=0; i <= index; i++){
p = slist[p].next;
}
return slist[p].data;
}

void print_slit_info(SLinkedList slist)
{
printf("The length of the static linked list is %d\nData: ", slist[0].data);
int p = slist[0].next;
while(p!=END){
printf("%d ", slist[p].data);
p = slist[p].next;
}
printf("\n");
}

int main()
{
SLinkedList slist;
SLinkedList backupList;
init_backup_list(backupList);
create_slist(slist, 5);
print_slit_info(slist);
insert(slist, backupList, 2, 10);
print_slit_info(slist);
deleteElement(slist, backupList, 2);
print_slit_info(slist);

return 0;
}


动态双向链表

#include<stdio.h>
#include<stdlib.h>

typedef struct DLNode *PDLNode;

typedef struct DLNode{
int data;
PDLNode pre;
PDLNode next;
}DLNode, *PDLNode;

void create_list(PDLNode *head, PDLNode *tail, int n)
{
*head = (PDLNode) malloc(sizeof(DLNode));
*tail = (PDLNode) malloc(sizeof(DLNode));

(*head)->pre = NULL;
(*head)->next = (*tail);
(*head)->data = n;

(*tail)->pre = (*head);
(*tail)->next = NULL;
(*tail)->data = n;

for(int i=0; i<n; i++){
PDLNode node = (PDLNode) malloc(sizeof(DLNode));
scanf("%d", &node->data);
node->pre = (*tail)->pre;
(*tail)->pre->next = node;
node->next = (*tail);
(*tail)->pre = node;
}

}

void insert(PDLNode head, int index, int value)
{
PDLNode p = head;
for(int i=0; i<index; i++)
p = p->next;
PDLNode node = (PDLNode)malloc(sizeof(DLNode));
node->data = value;

// 插入结点
node->pre = p;
p->next->pre = node;
node->next = p->next;
p->next = node;
}

void deleteElement(PDLNode head, int index)
{
PDLNode p = head;
for(int i=0; i<index; i++)
p = p->next;
PDLNode deleteNode = p->next;
deleteNode->next->pre = p;
p->next = deleteNode->next;
free(deleteNode);
}

int getElement(PDLNode head, int index)
{
PDLNode p = head->next;
for(int i=0; i<index; i++)
p = p->next;
return p->data;
}

void print_dlist_info(PDLNode head)
{
printf("The length of the doubly linked list is %d\nData: ", head->data);
PDLNode p = head->next;
while(p->next){
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}

void print_dlist_info_reverse(PDLNode tail)
{
printf("The length of the doubly linked list is %d\nReverse Data: ", tail->data);
PDLNode p = tail->pre;
while(p->pre){
printf("%d ", p->data);
p = p->pre;
}
printf("\n");
}

int main()
{
PDLNode head;
PDLNode tail;
create_list(&head, &tail, 5);
print_dlist_info(head);
print_dlist_info_reverse(tail);
insert(head, 3, 10);
print_dlist_info(head);
print_dlist_info_reverse(tail);
return 0;
}


PS: 关于循环链表和静态双向链表我就不一一给出代码,因为他们只是很细微的变化。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: