1 定义
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 链表解决了顺序表带来的弊端。 特点:按需向堆申请内存,不用了就释放空间 优点:增删数据不需要挪动数据;不存在空间浪费 缺点:每存一个数据都要存一个指针去链接后面的数据节点;不支持随机访问
2 分类
2.1 单向或双向
2.2 带头或不带头
2.3 循环或非循环
原理: phead头节点是一个指针,存的是链表第一个值的地址,
3 实现
3.1 不带头单向不循环链表SList
每个节点的结构体包括一个值和一个指向下一个节点的指针
3.1.1 代码实现
#pragma once
#include<stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
void SListPrint(SLTNode* phead);
SLTNode* BuyListNode(SLTDataType x);
void SListPushBack(SLTNode** phead, SLTDataType x);
void SListPushFront(SLTNode** phead, SLTDataType x);
void SListPopBack(SLTNode** pphead);
void SListPopFront(SLTNode** pphead);
SLTNode* SListFind(SLTNode* phead, SLTDataType x);
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);
void SListErase(SLTNode** pphead, SLTNode* pos);
void SListEraseAfter(SLTNode** pphead, SLTNode* pos);
void SListDestroy(SLTNode** pphead);
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
SLTNode* BuyListNode(SLTDataType x) {
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL) {
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SListPrint(SLTNode* phead) {
SLTNode* cur = phead;
while (cur != NULL) {
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void SListPushBack(SLTNode** phead, SLTDataType x) {
SLTNode* newnode = BuyListNode(x);
if (*phead == NULL) {
*phead = newnode;
}
else {
SLTNode* tail = *phead;
while (tail->next != NULL) {
tail = tail->next;
}
tail->next = newnode;
}
}
void SListPushFront(SLTNode** phead, SLTDataType x) {
SLTNode* newnode = BuyListNode(x);
newnode->next = *phead;
*phead = newnode;
}
void SListPopBack(SLTNode** pphead) {
assert(*pphead != NULL);
if ((*pphead)->next == NULL) {
free(*pphead);
*pphead = NULL;
}
else {
SLTNode* tail = *pphead;
while (tail->next->next) {
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
void SListPopFront(SLTNode** pphead) {
if (*pphead == NULL) {
return;
}
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
SLTNode* SListFind(SLTNode* phead, SLTDataType x) {
SLTNode* cur = phead;
while (cur) {
if (cur->data == x) {
return cur;
}
else {
cur = cur->next;
}
}
return NULL;
}
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
if (pos == *pphead) {
SListPushFront(pphead, x);
}
else {
SLTNode* newnode = BuyListNode(x);
SLTNode* prev = *pphead;
while (prev->next) {
if (prev->next == pos) {
break;
}
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
assert(pos);
SLTNode* newnode = BuyListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
void SListErase(SLTNode** pphead, SLTNode* pos) {
assert(pos);
assert(*pphead);
if (*pphead == pos) {
*pphead = pos->next;
free(pos);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos) {
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
void SListDestroy(SLTNode** pphead) {
SLTNode* cur = *pphead;
while (cur) {
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void TestSList1()
{
printf("TEST1:\n");
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushFront(&plist, 2);
SListPushFront(&plist, 7);
SListPushFront(&plist, 6);
SListPrint(plist);
SListPopBack(&plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPopFront(&plist);
SListPrint(plist);
printf("TEST1 END\n");
SListDestroy(&plist);
}
void TestSList2()
{
printf("TEST2:\n");
SLTNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushFront(&plist, 2);
SListPrint(plist);
SLTNode* pos = SListFind(plist, 1);
SListEraseAfter(&plist, pos);
SListErase(&plist, pos);
SListPrint(plist);
pos = SListFind(plist, 2);
SListInsert(&plist, pos, 5);
SListInsertAfter(&plist, pos, 9);
SListPrint(plist);
pos = SListFind(plist, 8);
printf("%p\n", pos);
printf("TEST2 END\n");
SListDestroy(&plist);
}
int main() {
TestSList1();
TestSList2();
return 0;
}
3.1.2 运行结果
3.2 带头双向循环链表List
每个节点的结构体包括一个值,一个指向上一个节点的指针和一个指向下一个节点的指针
3.2.1 代码实现
#pragma once
#include <stdio.h>
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
ListNode* ListCreate();
void ListDestroy(ListNode* pHead);
void ListPrint(ListNode* pHead);
void ListPushBack(ListNode* pHead, LTDataType x);
void ListPopBack(ListNode* pHead);
void ListPushFront(ListNode* pHead, LTDataType x);
void ListPopFront(ListNode* pHead);
ListNode* ListFind(ListNode* pHead, LTDataType x);
void ListInsert(ListNode* pos, LTDataType x);
void ListErase(ListNode* pos);
#include "List.h"
#include <assert.h>
#include <stdlib.h>
ListNode* ListCreate() {
ListNode* pHead = (ListNode*)malloc(sizeof(ListNode));
if (pHead) {
pHead->_next = pHead;
pHead->_prev = pHead;
}
return pHead;
}
void ListDestroy(ListNode* pHead) {
assert(pHead);
ListNode* tail = pHead->_prev;
while (tail != pHead) {
ListNode* prev = tail->_prev;
free(tail);
tail = prev;
}
free(pHead);
}
ListNode* BuyListNode(LTDataType x) {
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
if (newNode) {
newNode->_data = x;
newNode->_next = NULL;
newNode->_prev = NULL;
}
return newNode;
}
void ListPrint(ListNode* pHead) {
ListNode* cur = pHead->_next;
while (cur != pHead) {
printf("%d -> ", cur->_data);
cur = cur->_next;
}
printf("NULL\n");
}
void ListPushBack(ListNode* pHead, LTDataType x) {
ListNode* newNode = BuyListNode(x);
ListNode* tail = pHead->_prev;
tail->_next = newNode;
newNode->_prev = tail;
newNode->_next = pHead;
pHead->_prev = newNode;
}
void ListPopBack(ListNode* pHead) {
assert(pHead);
assert(pHead->_next != pHead);
ListNode* tail = pHead->_prev;
ListNode* prev = tail->_prev;
prev->_next = pHead;
pHead->_prev = prev;
free(tail);
}
void ListPushFront(ListNode* pHead, LTDataType x) {
ListNode* newNode = BuyListNode(x);
ListNode* head = pHead;
ListNode* next = head->_next;
newNode->_next = next;
next->_prev = newNode;
head->_next = newNode;
newNode->_prev = head;
}
void ListPopFront(ListNode* pHead) {
assert(pHead);
assert(pHead->_next != pHead);
ListNode* head = pHead;
ListNode* cur = head->_next;
ListNode* next = cur->_next;
head->_next = next;
next->_prev = head;
free(cur);
}
ListNode* ListFind(ListNode* pHead, LTDataType x) {
ListNode* cur = pHead->_next;
while (cur != pHead) {
if (cur->_data == x) {
return cur;
}
cur = cur->_next;
}
return NULL;
}
void ListInsert(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* newNode = BuyListNode(x);
ListNode* prev = pos->_prev;
newNode->_next = pos;
prev->_next = newNode;
pos->_prev = newNode;
newNode->_prev = prev;
}
void ListErase(ListNode* pos) {
assert(pos);
ListNode* prev = pos->_prev;
ListNode* next = pos->_next;
prev->_next = next;
next->_prev = prev;
free(pos);
}
#include "List.h"
void TestList1()
{
ListNode* plist = ListCreate();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
ListPopBack(plist);
ListPopBack(plist);
ListPopBack(plist);
ListPrint(plist);
ListPopBack(plist);
ListPrint(plist);
}
void TestList2()
{
ListNode* plist = ListCreate();
ListPushFront(plist, 1);
ListPushFront(plist, 2);
ListPushFront(plist, 3);
ListPushFront(plist, 4);
ListPrint(plist);
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPrint(plist);
ListNode* pos = ListFind(plist, 2);
if (pos)
{
ListErase(pos);
}
ListPrint(plist);
ListPopBack(plist);
ListPopBack(plist);
ListPopFront(plist);
ListPopFront(plist);
ListPrint(plist);
ListDestroy(plist);
plist = NULL;
}
int main()
{
TestList1();
printf("\n");
TestList2();
return 0;
}
3.2.2 运行结果
4 顺序表vs链表
4.1 区别(优/缺点):
4.1.1 顺序表:
优点: 1.支持随机访问; 2.CPU高速缓存命中率高; 缺点: 1.插入删除头部和中间元素的时间复杂度为O(n),效率低; 2.为避免频繁开辟内存空间,会以倍数形式开辟空间,与此同时会带来空间浪费的问题。
4.1.2 链表(带头双向循环)
优点: 1.效率高,任意位置插入删除元素的时间复杂度都为O(1); 2.按需申请和释放空间,没有空间浪费; 缺点: 1.不支持随机访问,从而导致一些算法不适用, 比如二分查找,部分排序等; 2.每添加一个值都会附带存储链接指针,也会占用一定的资源(不过可忽略); 3.CPU高速缓存命中率低。
|