图说数据结构之线性表链式存储
本人在学习数据结构过程中的一点心得,与大家共享。
数据结构中的线性表顺序存储,在插入、删除数据时,需要移动表中其他数据,不够灵活。而链式存储的插入、删除操作都很方便。链式存储,顾名思义就是用一条链条将用户数据穿起来,在c语言中就是利用指针操作。
一、简单结构描述
简单的链式存储链中的节点由用户数据和一个指向下一个节点的指针组成。定义如下图所示:
存储数据后的结构形态如下图所示。
从上图形式上看,数据链上包括head头和node节点。形象直观,很容易理解。实际编程中,head和node的数据结构相同,数据结构定义在头文件中。
typedef struct Teacher
{
int age;
char name[64];
}Elemtype;
struct _tag_LinkList
{
Elemtype data;
struct _tag_LinkList *node;
};
typedef struct _tag_LinkList LinkList;
指针node(即图中的next)是指向本身数据结构的指针。理解上复杂点,其实就是一个地址结构的数据(32bit系统中4个字节),里面放着本数据的地址。
编程中,链表初始化时malloc一块空间放head头,insert插入数据的时候,再malloc一块空间放置用户数据,然后用指针穿起来。相关的两个函数如下:
LinkList* LinkList_Create()
{
LinkList* t = NULL;
t = malloc(sizeof(LinkList));
if(t == NULL)
{
printf("LinkList_Create failed!");
return NULL;
}
memset(t, 0, sizeof(LinkList));
t->node = NULL;
return t;
}
int LinkList_Insert(LinkList* list, Elemtype* node, int pos)
{
int i=0;
LinkList* current = NULL;
if(list==NULL || node==NULL || pos<0)
{
printf("LinkList_Insert error!");
return -1;
}
LinkList* t = malloc(sizeof(LinkList));
memset(t,0,sizeof(LinkList));
memcpy(&(t->data),node,sizeof(Elemtype));
current = list;
for(i=0;i<pos && (current->node!=NULL);i++)
{
current = current->node;
}
t->node = current->node;
current->node = t;
return pos;
}
完整的代码实现见资源压缩包中的linklist-V01。
二、结构改进
上述链表的描述存在以下不足:
- 用户数据定义在头文件中,数据没有保密性;
- 用户数据嵌入在数据定义中,没有扩展性,不够灵活;
- 没有链表长度信息,若要得到链表长度,则需遍历整个链表,性能不好。
- 头head中只有一个指针,浪费了一块空间没有利用。
针对上述问题,进行结构优化,链表结构如下图:
改进后的结构解决了上述的全部四个缺点,并且head头和node节点结构完全一样,方便了编程。
链表初始化时需再多malloc一块地址用于存放length链表长度。数据定义、Create和Insert函数实现如下:
typedef struct _tag_LinkListNode
{
void* data;
struct _tag_LinkListNode *next;
}LinkListNode;
LinkList* LinkList_Create()
{
unsigned int *length;
LinkListNode* t = NULL;
t = malloc(sizeof(LinkListNode));
if(t == NULL)
{
printf("LinkList_Create failed!");
return NULL;
}
memset(t, 0, sizeof(LinkListNode));
t->next = NULL;
length = malloc(sizeof(unsigned int));
memset(length,0,sizeof(unsigned int));
t->data = length;
return t;
}
int LinkList_Insert(LinkList* list, void* node, int pos)
{
int i=0;
LinkListNode* t = list;
LinkListNode* elem = NULL;
LinkListNode* current = t;
if(list==NULL || node==NULL || pos<0)
{
printf("LinkList_Insert error!");
return -1;
}
elem = malloc(sizeof(LinkListNode));
memset(elem,0,sizeof(LinkListNode));
elem->data = node;
//move current
for(i=0;i<pos && (current!=NULL);i++)
{
current = current->next;
}
elem->next = current->next;
current->next = elem;
(*((unsigned int*)(t->data)))++;
return pos;
}
完整的改进代码实现见资源压缩包中的linklist-V02。
三、进一步优化
据说在工程项目开发中流行的链表结构是,用户数据结构包含next指针。据说也是Linux上使用的数据结构。
在头文件中定义的node类型就是一个指针,指向本身结构的指针类型,如下图:
typedef struct _tag_LinkListNode
{
struct _tag_LinkListNode *Next;
}LinkListNode;
在用户数据的头部添加上这个node类型数据,这样,node指针的地址就是整个用户数据的首地址。如图:
链表的head头包括node类型数据和链表长度length。如图:
链表实现后的链表结构如下图:
head头数据定义、Create和Insert函数实现如下:
typedef struct _tag_LinkList
{
LinkListNode handle;
int length;
}TLinkList;
LinkList* LinkList_Create()
{
TLinkList* t = NULL;
t = malloc(sizeof(TLinkList));
if(t == NULL)
{
printf("LinkList_Create failed!");
return NULL;
}
memset(t, 0, sizeof(TLinkList));
(t->handle).Next = NULL;
t->length = 0;
return t;
}
int LinkList_Insert(LinkList* list, LinkListNode* node, int pos)
{
int i=0;
TLinkList* t = (TLinkList*)list;
LinkListNode* current = NULL;
if(list==NULL || node==NULL || pos<0)
{
printf("LinkList_Insert error!");
return -1;
}
current = &(t->handle);
//move current
for(i=0;i<pos && (current->Next!=NULL);i++)
{
current = current->Next;
}
node->Next = current->Next;
current->Next = node;
t->length++;
return pos;
}
完整的优化代码实现见资源压缩包中的linklist-V03。
四、进一步分析
从图示和数据结构代码可直观看出,进一步优化的“用户数据结构包含next指针”的结构中,串连数据用的node指针不是指向整个数据结构的指针,而只是指向它本身的指针,就是一个地址:32位系统下4个字节,64位系统下8个字节。
因此,head头和用户数据结构可以等效成下图:
相当于在head头结构及用户数据结构的首部定义了一个4字节空间(32位系统),存放地址,存放指向下一个用户数据的地址,这一组4字节地址就是这个线性表的链条。
head头结构及用户数据结构中这个地址定义为unsigned int类型数据:
typedef struct _tag_LinkList
{
unsigned int handle;
int length;
}TLinkList;
typedef struct Teacher
{
unsigned int node;
int age;
char name[64];
}Teacher;
经编程验证可以实现,过程中有点绕,不断进行类型转换。完整的代码实现见资源压缩包中的linklist-V04,只用于研究和娱乐。