链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
- 链表结构在逻辑上是连续的,但是在物理上不一定是连续的
- 现实中的结点一般都是从堆上申请出来的
- 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
链表分类
简单分类来讲,我们把链表的属性分为以下三种:
- 单向或者双向
- 有头或者无头
- 循环或者非循环
根据2^3=8推出链表一共分为8种类型,而在这8种链表中最常见的是:
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构。
有头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
链表的实现
单向无头非循环链表
假设我们需要创建一个由个人信息组成的链表。
第一步:先创建关于person的结构体,并将其包含在listNode链表对象之中作为成员。
struct person {
char name[10];
int year;
char sex;
};
struct listNode {
person info;
listNode* next;
};

第二步:为listNode提供操作函数
/*
开辟内存
*/
list* createList(person info) {
/*
不直接使用malloc分配内存
listNode l;
l.info = info;
l.next = NULL;
return &l;
*/
listNode* l = (listNode*)malloc(sizeof(listNode));
l->info = info;
l->next = NULL;
return l;
}
/*
尾部插入
*/
void pushback(person info, listNode** head) {
listNode* add = createList(info);
if (*head == NULL) {
*head = add;
}
else {
listNode* tail = *head;
while (tail->next != NULL) {
tail = tail->next;
}
tail->next = add;
}
}
/*
头部插入
*/
void pushfront(person info, listNode** head) {
listNode* add = createList(info);
if (*head == NULL) {
*head = add;
}
else {
add->next = *head;
*head = add;
}
}
/*
尾部删除
*/
void popback(listNode** head) {
if (*head != NULL) {
listNode* temp = *head;
listNode* prev = temp;
while (temp->next != NULL) {
prev = temp;
temp = temp->next;
}
prev->next = NULL;
free(temp); //释放内存
temp = NULL;
}
}
/*
头部删除
*/
void popfront(listNode** head) {
if (*head != NULL) {
listNode* temp = *head;
*head = (*head)->next;
free(temp);
temp = NULL;
}
}
/*
打印链表
*/
void printList(listNode* head) {
assert(head != NULL); //断言assert(0) 则abort();退出
while (head != NULL) {
printf("%s %d %c\n", head->info.name, head->info.year, head->info.sex);
head = head->next;
}
}
第三步:在main函数中操作链表
int main() {
person p = { "XiaoMing",8,'M' };
person p2 = { "XiaoHong",8,'F' };
person p3 = { "XiaoGang",8,'M' };
listNode* l = NULL;
pushback(p, &l);
pushback(p2, &l);
pushback(p3, &l);
pushfront(p3, &l);
popfront(&l);
popback(&l);
printList(l);
}
Output:
XiaoMing 8 M
XiaoHong 8 F
双向有头循环链表
现在我们创建班级的双向有头循环链表。
第一步:构造cls结构体,并对cls重载符号==函数(在后面进行Search操作时会用到)。并将cls包含在我们的listNode链表结构体内。
struct cls {
int id;
int memnum;
//重载==符号函数
bool operator == (const cls& clazz) {
return clazz.id == this->id && this->memnum == clazz.memnum;
}
};
struct listNode {
cls clazz;
listNode* pre;
listNode* next;
};

第二步:为listNode提供操作函数,具体作用查看注释。
/*
申请空间
*/
listNode* createList(cls clazz) {
listNode* node = (listNode*)malloc(sizeof(listNode));
node->clazz = clazz;
return node;
}
/*
初始创建链表头节点
*/
listNode* headInit(void) {
listNode* head = createList(cls{0,0});
head->next = head;
head->pre = head;
return head;
}
/*
查询cls对应节点
*/
listNode* listSearch(listNode** list, cls clazz) {
listNode* pos = *list;
while (!(pos->clazz == clazz)) {
pos = pos->next;
if (pos == *list) {
return NULL;
}
}
return pos;
}
/*
删除指定cls节点
*/
void listErase(listNode* list, cls clazz) {
listNode* pos = listSearch(&list,clazz);
pos->pre->next = pos->next;
pos->next->pre = pos->pre;
free(pos);
}
/*
头插
*/
void pushFront(listNode** head,cls clazz) {
assert(*head != NULL);
listNode* node = createList(clazz);
listNode* phead = (*head)->next; //头节点后的第一位单元
phead->pre = node;
(*head)->next = node;
node->next = phead;
node->pre = *head;
}
/*
尾插
*/
void pushBack(listNode** head, cls clazz) {
listNode* ptail = (*head)->pre;
listNode* node = createList(clazz);
ptail->next = node;
node->pre = ptail;
node->next = *head;
(*head)->pre = node;
}
/*
头删
*/
void popFront(listNode** head) {
assert(*head != NULL);
listErase(*head,(*head)->next->clazz);
}
/*
尾删
*/
void popBack(listNode** head) {
assert(*head != NULL);
listErase(*head, (*head)->pre->clazz);
}
/*
指定cls前插入
*/
void insert(listNode** head,cls clazz) {
listNode* node = createList(clazz);
listNode* pos = listSearch(head, clazz);
pos->pre->next = node;
node->pre = pos->pre;
pos->pre = node;
node->next = pos;
}
/*
打印链表
*/
void listPrint(listNode* head) {
assert(head != NULL);
listNode* phead = head->next;
while (phead != head) {
printf("Class:%d Mem:%d\n", phead->clazz.id, phead->clazz.memnum);
phead = phead->next;
}
}
第三步:在main函数中进行操作测试。
int main() {
cls c1 = { 1,50 };
cls c2 = { 2,60 };
cls c3 = { 3,40 };
cls c4 = { 4,40 };
listNode* list = headInit();
printf("==================\n尾插\n");
pushBack(&list, c1);
pushBack(&list, c2);
listPrint(list);
printf("==================\n头插\n");
pushFront(&list, c3);
pushFront(&list, c4);
listPrint(list);
printf("==================\n删c1\n");
listErase(list, c1);
listPrint(list);
printf("==================\n头删\n");
popFront(&list);
listPrint(list);
printf("==================\n尾删\n");
popBack(&list);
listPrint(list);
}
Output:
==================
尾插
Class:1 Mem:50
Class:2 Mem:60
==================
头插
Class:4 Mem:40
Class:3 Mem:40
Class:1 Mem:50
Class:2 Mem:60
==================
删c1
Class:4 Mem:40
Class:3 Mem:40
Class:2 Mem:60
==================
头删
Class:3 Mem:40
Class:2 Mem:60
==================
尾删
Class:3 Mem:40
小结
因其长度可变,内存不连续等优点,链表的运用将极大提高对多个同一结构的操作效率。