单向链表
链表是一种链式存储结构,物理层面是一段地址不一定连续的存储空间
结点是链表的最小单位,每个结点都有有唯一后继。
概念:
链表的存储结构
结点用LNode结构体表示,由存储数据data和指向下一个结构体的指针域组成。
特别的头指针使用*LinkList来表示一个链表,LNode用于表示节点,这样对链表和节点有很好的区分,可以提高代码的可读性。在后续的LNode *LocateElem(LinkList L,ElemType e);
函数中我们可以发现传入的参数为链表,返回节点指针。
1 2 3 4 5
| typedef int ElemType; typedef struct LNode{ ElemType data; struct LNode *next; }LNode,*LinkList;
|
链表的基本操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Status InitList(LinkList &L);
Status HeadCreateList(LinkList &L);
Status TailCreateList(LinkList &L);
Status GetElem(LinkList L,int i,ElemType &e);
LNode *LocateElem(LinkList L,ElemType e);
Status ListInsert(LinkList &L,int i,ElemType e);
Status ListDelete(LinkList &L,int i);
Status DeleteNode(LNode &p);
Status PrintList(LinkList L);
|
链表初始化
在链表的初始化中,我们会申请一块内存,作为头节点,L指针存储头结点的地址。链表此时无元素,所以头节点的后继设为NULL
。
1 2 3 4 5 6 7 8
| Status InitList(LinkList &L) { L=(LinkList)malloc(sizeof(LNode)); if(L==NULL) return ERROR; L->next==NULL; return OK; }
|
头插法和尾插法
头插法:在链表头结点之后进行插入。因此头插法得到的数据是逆序存放的。
头插法直接在头结点后面插入,执行效率高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Status HeadCreateList(LinkList &L) { int num; ElemType data; std::cin>>num; for(int i=0;i<num;i++){ std::cin>>data; LNode *s=(LNode *)malloc(sizeof(LNode)); if(!s) return ERROR; s->data=data; s->next=L->next; L->next=s; } return OK; }
|
尾插法:在链表尾部进行插入操作,得到顺序序列
尾插法每次需要从头开始逐个访问链表结点,将指针定位到链表的尾部,在尾部进行插入操作,为了提高效率,我们可以设置一个尾指针,指向链表尾部元素,每次插入元素后更新尾指针的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Status TailCreateList(LinkList &L) { LNode *tail=L; int num; ElemType data; std::cin>>num; for(int i=0;i<num;i++){ std::cin>>data; LNode *s=(LNode *)malloc(sizeof(LNode)); s->next=NULL; if(!s) return ERROR; s->data=data; tail->next=s; tail=s; } return OK; }
|
链表的删除操作
删除第i个元素。首先使用变量i记录当前访问的元素,开始时p指向首元节点,索引值为1。
当访问到链表尾部或当访问到第i-1的元素时停止向后访问。将第i-1个元素的后继设为第i-2个元素,释放第i个元素的内存空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Status ListDelete(LinkList &L,int i) { int j=1; LNode* p=L->next; while(p&&j<i-1){ p=p->next; j++; } if(!p || j>i) return ERROR; LNode *s=p->next; p->next=s->next; free(s); return OK; }
|
给定一个节点指针,如何将它删除呢?常规操作肯定是pre->next=pre->next->next;
使用pre前驱节点来删除元素,当我们并没有待删除节p的前驱节点信息。我们只能删除该节点的后继节点,利用这一点,我们可以将后继节点的值赋给要删除的节点P,然后删除后继节点。使用这招偷梁换柱,便可以达到删除节点的效果。
1 2 3 4 5 6 7 8 9
| Status DeleteNode(LNode* p) { LNode *s=p->next; p->data=s->data; p->next=s->next; free(s); return OK; }
|
链表的插入操作
在第i个位置进行插入操作。找到第i-1个节点p,待插入节点指向第i个节点(p->next),第i-1个节点再指向待插入节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Status ListInsert(LinkList &L,int i,ElemType e) { int j=1; LNode* p=L->next; while(p&&j<i-1){ p=p->next; j++; } LNode* s=(LNode*)malloc(sizeof(LNode)); if(!s) return ERROR; s->data=e; s->next=p->next; p->next=s; return OK; }
|
链表的遍历
1 2 3 4 5 6 7 8 9 10
| Status PrintList(LinkList L) { LNode *p=L->next; while(p){ std::cout<<p->data<<"->"; p=p->next; } std::cout<<std::endl; return OK; }
|
单向循环链表
单项循环链表的尾节点指向头指针,而不是指向NULL。
链表结构
1 2 3 4 5
| typedef struct Node { int data; struct Node* next; }Node;
|
初始化
1 2 3 4 5 6 7
| struct Node* InitList() { struct Node* L=(struct Node*)malloc(sizeof(struct Node)); L->next=L; L->data=0; return L; }
|
头插法和尾插法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void HeadInsert(struct Node* L,int data) { struct Node* node=(struct Node*)malloc(sizeof(struct Node)); node->data=data; node->next=L->next; L->next=node; L->data++; } void TailInsert(struct Node* L,int data) { struct Node* tail=L; struct Node* node=(struct Node*)malloc(sizeof(struct Node)); node->data=data; while(tail->next!=L) { tail=tail->next; } node->next=L; tail->next=node; L->data++; }
|
删除操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Status DeleteVal(struct Node* L,int value) { Node* pre=L; Node* cur=L->next; while(cur!=L){ if(cur->data==value){ pre->next=cur->next; free(cur); L->data--; return OK; } pre=cur; cur=cur->next; } printf("cant not find the value equal %d in the linklist\n",value); return ERROR; }
|
遍历链表
1 2 3 4 5 6 7 8 9 10
| void PrintList(struct Node* L) { struct Node* node=L->next; printf("L->"); while(node!=L){ printf("%d->",node->data); node=node->next; } printf("L\n"); }
|
插入数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Status InsertNodeAfterVal(struct Node* L,int value,int node_data) { struct Node* cur=L->next; struct Node* node=(struct Node*)malloc(sizeof(struct Node)); node->data=node_data; while(cur!=L){ if(cur->data==value){ node->next=cur->next; cur->next=node; return OK; } cur=cur->next; } printf("cant not find the value equal %d in the linklist\n",value); free(node); return ERROR; }
|
linux内核链表
链表
linux中的链表将数据域与指针域进行分离。
通过list_head怎么访问其他成员变量?
这就要依靠container_of宏了。指针域相对于结构体首地址的偏移量是固定的
指针域地址-相当偏移地址=结构体首地址。偏移量求解的办法offset
宏
1 2 3
| #define container_of(ptr,type,member) \ const typeof((type *)0->member) *_ptr=(ptr); \ (type *)((char *)_ptr-offset(type,member))
|
offset是内置的函数,也可以用上面的方法实现
1
| #define offset(type,member) (unsigned long)(&(type)(0)->member)
|
使用0地址作为结构体的起始地址,成员的地址就是相对与0号地址的偏移地址
链表头
静态创建链表头
1 2 3 4
| struct Node Head{ .data=data; .list=List_Head_Init(Head.list), }
|
动态创建链表头
1 2 3
| struct Node* HeadNode=malloc() Head->data; List_Head_Init(Head->list);
|
添加节点
在某一位置插入
1
| list_add(struct list_head *new,struct list_head *head)
|
在尾部插入
1
| list_add_tail(struct list_head *new,struct list_head *head);
|
删除节点
1
| list_del(struct list_head *entry)
|
具体实现
1 2 3 4 5 6 7
| static inline void __list_del(struct list_head *prev,struct list_head *next){ next->prev=prev; prev->next=next; } static inline void list_del(struct list_head *entry){ __list_del(entry->prev,entry->next); }
|
是否为空
1
| list_empty(struct list_head *head);
|
遍历链表
p表示循环中的临时变量,list表示遍历的起始位置
遍历实体
可以这样遍历
1 2 3 4
| list_for_each(p,list){ entry=list_entry() }
|
进行封装
1 2 3 4 5 6 7
|
list_for_each_entry(pos,head,member)
|
1 2 3
| list_for_each_entry(pos,head,member){ printf(pos->member) }
|
反向遍历链表
1
| list_for_each_reverse(pos,head,member)
|
线程安全的链表