資料結構與演算法:企業級連結串列實現(超詳細)
企業級連結串列介紹
如果我們使用原始的C語言寫連結串列的話,資料型別是被固定死的,如果業務換了 需要另一種資料型別,我們又得重新在寫一個連結串列,當然我們可以使用void* 萬能指標,因為void* 可以接受任意型別的指標,那麼我們就可以接受任意型別的資料的地址了,我們只需要維護使用者提供的資料地址就可以。詳細介紹請看:資料結構與演算法:單鏈表(利用萬能指標實現對任意型別資料進行操作)。今天我們介紹另外一種方法,讓我們的寫的連結串列不受業務結構而限制,在我們用的資料型別發生改變時並不需要去改變底層的連結串列結構。這就今天要和大家分享的企業級連結串列,感覺這種思想太讚了。我們在c語言中一般使用結構體作為複合資料型別,可以定義我們需要的資料集。如果我們將具體的業務資料放到LinkNode 連結串列結點中去,就會遇到前面分析的問題,業務和底層程式碼實現 耦合的太緊了。企業級連結串列就是來解決這個問題。
企業級連結串列分析
接前面分析的問題,那麼為何不把業務資料域分離出來呢?連結串列結點LinkNode中只放next指標域。
typedef struct LinkNode
{
struct LinkNode* next;
}LinkNode;
那我們的資料業務資料呢?放哪裡呀?下面就是企業級連結串列的高明處。你定義的業務資料,該怎麼定義就怎麼定義,只需將LinkNode 作為你定義的業務資料型別的第一個成員 就行了。比如說,下面定義一個學生資料型別。由於LinkNode 放在我們自定義的資料型別Student型別的第一個成員,那麼&LinkNode和&Student 他們是一樣的!他們的起始地址是重合
typedef struct Student
{
LinkNode node;
char name[64];
int age;
}Student;
企業級連結串列程式碼實現
相關演算法還是和正常的單鏈表差不多,只是使用者使用的時候,按照要求使用就ok,LinkNode做為使用者定義的業務資料的第一個成員,然後可以使用了。
CompanyLinkList.h
#pragma once
#ifndef __COMPANY_LINKLIST_H__
#define __COMPANY_LINKLIST_H__
typedef enum {TRUE,FASLE} BOOLEAN ;//成功狀態 FALSE 不成功 TRUE成功
typedef enum { ERROR, OK } STATUS; //狀態資訊 ERROR 發生錯誤 OK 一切正常
typedef struct LinkNode
{
struct LinkNode* next;
}LinkNode;
typedef struct LinkList
{
LinkNode head;
int size;
}LinkList;
//建立連結串列並初始化
LinkList* Create_LinkList();
//插入資料 根據位置插入
int Insert_LinkList(LinkList* list,int pos, LinkNode* data);
//刪除資料 根據位置刪除
int Remove_LinkList(LinkList* list, int pos);
//查詢資料,根據資料內容
int Find_LinkList(LinkList* list, LinkNode* data,int (*Compare_Function)(LinkNode*,LinkNode*));
//遍歷連結串列
int Foreach_LinkList(LinkList* list, void(*Foreach_Function)(LinkNode*));
//清空資料 連結串列仍然可用
int Clear_LinkList(LinkList* list);
//銷燬連結串列 連結串列不可用
int Destroy_LinkList(LinkList* list);
#endif // !__COMPANY_LINKLIST_H__
CompanyLinkList.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "CompanyLinkList.h"
//建立連結串列並初始化
LinkList* Create_LinkList()
{
LinkList* list = (LinkList*)malloc(sizeof(LinkList));
list->size = 0;
list->head.next = NULL;
return list;
}
//插入結點資料的地址 根據位置插入,pos從1開始
int Insert_LinkList(LinkList* list, int pos, LinkNode* data)
{
if (NULL == list || NULL==data)
{
return ERROR;
}
//位置太小插入到 第一個位置
if (pos < 1)
{
pos = 1;
}
//位置太大插入到 尾部
if (pos > list->size)
{
pos = list->size + 1;
}
//找到插入位置的前驅結點
LinkNode* pre = &list->head;
for (int i = 1; i < pos; i++)
{
pre = pre->next;
}
data->next = pre->next;
pre->next = data;
list->size++;
return OK;
}
//刪除資料 根據位置刪除 pos 從1 開始
int Remove_LinkList(LinkList* list, int pos)
{
//list為NULL 或者位置非法 直接返回
if (NULL == list || pos < 1 || pos > list->size)
{
return ERROR;
}
LinkNode* pre = &list->head;
//同樣找到刪除結點的前驅
for (int i = 1; i < pos; i++)
{
pre = pre->next;
}
//將不要的元素略過,因為我們只維護使用者提供的資料的地址,釋放記憶體不歸我們管
pre->next = pre->next->next;
list->size--;
return OK;
}
//查詢資料,根據資料內容,返回時連結串列中的第幾個元素,從1開始計數 ,沒找到返回-1
//Compare_Function 使用者提供 結點元素比較的回撥函式
int Find_LinkList(LinkList* list, LinkNode* data, int(*Compare_Function)(LinkNode*, LinkNode*))
{
if (NULL == list || NULL == data || NULL == Compare_Function)
{
return ERROR;
}
LinkNode* node = list->head.next;
int flag = 0;// 是否查詢到的標誌
int index = 1;
while (node!=NULL)
{
if (Compare_Function(node, data))
{
flag = 1;
break;
}
node = node->next;
index++;
}
return flag ? index : -1;
}
//遍歷連結串列
//Foreach_Function 使用者提供的遍歷連結串列元素的回撥函式
int Foreach_LinkList(LinkList* list, void(*Foreach_Function)(LinkNode*))
{
if (NULL == list || NULL == Foreach_Function)
{
return ERROR;
}
LinkNode* node = list->head.next;
while (NULL!=node)
{
Foreach_Function(node);
node = node->next;
}
return OK;
}
//清空資料 連結串列仍然可用
int Clear_LinkList(LinkList* list)
{
if (NULL == list)
{
return ERROR;
}
list->size = 0;
list->head.next = NULL;
return OK;
}
//銷燬連結串列 連結串列不可用
int Destroy_LinkList(LinkList* list)
{
if (NULL == list)
{
return ERROR;
}
free(list);
list = NULL;
return OK;
}
//使用者使用 企業級連結串列,只需要在自己定義的型別中將LinkNode 作為第一個成員即可使用
//這樣就將 具體的業務和底層連結串列演算法 進行分離了
typedef struct Student
{
LinkNode node;
char name[64];
int age;
}Student;
//使用者定義遍歷的回撥函式
void Print_Student(LinkNode* node)
{
Student* stu = (Student*)node;
printf("姓名:%s,年齡%d\n",stu->name,stu->age);
return;
}
//使用者定義結點的比較回撥函式
int Compare_Student(LinkNode* node1, LinkNode* node2)
{
Student* stu1 = (Student*)node1;
Student* stu2 = (Student*)node2;
//年齡和名字相同才相同
if (stu1->age == stu2->age && 0 == strcmp(stu1->name, stu2->name))
{
return 1;
}
return 0;
}
int main(int argc, char *argv[])
{
//企業連結串列使用
Student s1, s2, s3, s4, s5;
s1.age = 11;
s2.age = 12;
s3.age = 13;
s4.age = 14;
s5.age = 15;
strcpy(s1.name, "aaa");
strcpy(s2.name, "bbb");
strcpy(s3.name, "ccc");
strcpy(s4.name, "ddd");
strcpy(s5.name, "eee");
//建立連結串列
LinkList* list = Create_LinkList();
//插入
Insert_LinkList(list, 1, (LinkNode*)&s1);
Insert_LinkList(list, 2, (LinkNode*)&s2);
Insert_LinkList(list, 3, (LinkNode*)&s3);
Insert_LinkList(list, 4, (LinkNode*)&s4);
Insert_LinkList(list, 5, (LinkNode*)&s5);
printf("插入5條資料後遍歷:\n");
Foreach_LinkList(list, Print_Student);
//查詢
Student s6;
s6.age = 15;
strcpy(s6.name, "eee");
int index = Find_LinkList(list,(LinkNode*)&s6, Compare_Student);
printf("查詢的元素name=eee,age=15,是連結串列中第%d個元素\n",index);
//刪除元素
Remove_LinkList(list, 1);
printf("刪除第1個結點後遍歷:\n");
Foreach_LinkList(list, Print_Student);
return 0;
}