小肥柴慢慢学习数据结构笔记(C篇)(2-1 单链表SingleLinkedList self版本实现(1))
目录
2-1 为啥要有链表
- 在1篇中“动态”数组ArrayList的学习中,我们发现这个动态是依靠resize操作实现的,非真正意义上的动态,因为我们需要随时关注size和capacity之间的关系。
- 虽然ArrayList的查询效率非常高,但是插入和删除开销大。
针对以上弱点,给出一个新的数据结构–链表。
2-2 出列ADT
- 节点struct
老样子,不会编码先写中文,然后翻译
struct 节点{
(1)存放的数据
(2)下一个节点的位置(地址)
}
可能需要一个虚拟头结点(DummyHead),方便编程。
struct Node{
ElementType Element; //数据
Position Next; //下一个节点地址
};
typedef struct Node *PrtToNode; //仿照老外的写法,其实也可以直接用*
typedef PrtToNode List; //链表虚拟头结点,代表了一个链表
typedef PrtToNode Position; //链表上某个位置的节点
有关Head和Tail的问题,其实本质就是让每一个节点都有前驱(prev)节点和后驱(next不为空)节点,让各种功能实现更加方便,这一节我们仅使用一个Head,你会发现在尾插操作时,有Tail是一件非常幸福的事情。—所有的设计都是为了更好的实现功能,尽量的避免bug。
- 操作
List createList(); //新生成一个链表
int IsEmpty(List L); //链表判空
Position Find(ElementType X, List L); //找到指定元素的第一个节点
Position FindPrevious(ElementType X, List L); //找到指定元素的第一个节点的前驱节点,删除有用
void Insert(ElementType X, List L, Position P); //在节点P后面插入新的元素X
void InsertFirst(ElementType X, List L); //头插
void InsertLast(ElementType X, List L); //尾插
void Delete(ElementType X, List L); //删除指定元素第一个节点
void DeleteList(List L); //清空链表
Position Advance(Position P); //获得Next节点
Position Header(List L); //获得虚拟头结点
Position First(List L); //获得真正存有数据的第一个节点
Position Last(List L); //获得最后一个存有数据的节点
ElementType Retrieve(Position P); //获取当前节点数据
void PrintList(List L); //打印节点
注: 有的朋友在Node的基础上再封装(Head + Tail)或者(prev+next),都是设计优化,本篇先实现一个简单版本,对不熟悉的同学来讲,代码是可以一点点慢慢优化的。
2-3 边想边写
其实链表的操作就是对节点指向的操作,关键点在于
(1)要保存下当前被操作的节点对象地址
(2)改变指向关系
(3)视情况释放暂存节点,或者更改游标位置(first/last,中间量tmp)
找到规律,自己在纸上写明白,画明白了,并没有那么玄乎。
- 插入
void Insert(ElementType X, List L, Position P){
Position TmpCell = (Position)malloc(sizeof(struct Node));
if(L == NULL) //空指针防不胜防
printf("\ninsert list failed, out of memery!\n");
TmpCell->Element = X;
TmpCell->Next = P->Next;
P->Next = TmpCell;
}
- 删除
关键在于找到prev节点
Position FindPrevious(ElementType X, List L){
Position P = L; //从虚拟头结点开始遍历,避开各种判断
while(P->Next != NULL && P->Next->Element != X)
P = P->Next;
return P;
}
void Delete(ElementType X, List L){
Position Prev, TmpCell;
Prev = FindPrevious(X, L);
if(Prev != NULL){
TmpCell = Prev->Next;
Prev->Next = Prev->Next->Next;
free(TmpCell);
}
}
- 遍历
遍历也是要根据具体要求来的,可以从DummyHead开始,也可以从First开始,重点是怎么好用怎么来。
Position P = L->Next; //从L开始也行
while(P!=NULL){
//...
P = P->Next;
}
- 现阶段所有代码
(1)SingleLinkedList.h
typedef int ElementType;
#ifndef _List_H
#define _List_H
struct Node;
typedef struct Node *PrtToNode;
typedef PrtToNode List;
typedef PrtToNode Position;
List createList();
int IsEmpty(List L);
Position Find(ElementType X, List L);
Position FindPrevious(ElementType X, List L);
void Insert(ElementType X, List L, Position P);
void InsertFirst(ElementType X, List L);
void InsertLast(ElementType X, List L);
void Delete(ElementType X, List L);
void DeleteList(List L);
Position Advance(Position P);
Position Header(List L);
Position First(List L);
Position Last(List L);
ElementType Retrieve(Position P);
void PrintList(List L);
#endif
(2)SingleLinkedList.c
#include <stdlib.h>
#include <stdio.h>
#include "SingleLinkedList.h"
struct Node{
ElementType Element;
Position Next;
};
List createList(){
Position L = (Position)malloc(sizeof(struct Node));
if(L == NULL){
printf("\ncreate list failed, out of memery!\n");
return NULL;
}
L->Next = NULL;
return L;
}
int IsEmpty(List L){
return L->Next == NULL;
}
Position Find(ElementType X, List L){
Position P = L->Next;
while(P!= NULL && P->Element != X)
P = P->Next;
return P;
}
Position FindPrevious(ElementType X, List L){
Position P = L;
while(P->Next != NULL && P->Next->Element != X)
P = P->Next;
return P;
}
void Insert(ElementType X, List L, Position P){
Position TmpCell = (Position)malloc(sizeof(struct Node));
if(L == NULL)
printf("\ninsert list failed, out of memery!\n");
TmpCell->Element = X;
TmpCell->Next = P->Next;
P->Next = TmpCell;
}
void InsertFirst(ElementType X, List L){
Position TmpCell = (Position)malloc(sizeof(struct Node));
if(L == NULL)
printf("\ninsert list first failed, out of memery!\n");
TmpCell->Element = X;
TmpCell->Next = L->Next;
L->Next = TmpCell;
}
void InsertLast(ElementType X, List L){
Position last = Last(L);
if(last != NULL)
Insert(X, L, last);
else{
Position P = (Position)malloc(sizeof(struct Node));
P->Element = X;
P->Next = NULL;
L->Next = P;
}
}
void Delete(ElementType X, List L){
Position Prev, TmpCell;
Prev = FindPrevious(X, L);
if(Prev != NULL){
TmpCell = Prev->Next;
Prev->Next = Prev->Next->Next;
free(TmpCell);
}
}
void DeleteList(List L){
Position P, TmpCell;
P = L->Next;
L->Next = NULL;
while(P != NULL){
TmpCell = P->Next;
free(P);
P = TmpCell;
}
}
Position Advance(Position P){
return P==NULL ? NULL : P->Next;
}
Position Header(List L){
return L;
}
Position First(List L){
return L == NULL ? NULL : L->Next;
}
Position Last(List L){
if(IsEmpty(L))
return NULL;
Position P = L->Next;
while(P!=NULL && P->Next != NULL)
P = P->Next;
return P;
}
ElementType Retrieve(Position P){
return P==NULL ? NULL : P->Element;
}
void PrintList(List L){
Position P = L->Next;
printf("\nDummyHead->");
while(P!=NULL){
printf("[%d]->", P->Element);
P = P->Next;
}
printf("NULL\n");
}
(3)Main.c 测试
#include <stdio.h>
#include <stdlib.h>
#include "SingleLinkedList.h"
int main(int argc, char *argv[]) {
Position last;
printf("\n===============test create && insert :===================\n");
int i;
List list = createList();
Position P = list;
for(i = 0; i < 10; i++){
Insert(i, list, P);
P = Advance(P);
}
PrintList(list);
printf("\n===============test insert first :===================\n");
InsertFirst(-1, list);
PrintList(list);
printf("\n===============test delete :=========================\n");
for(i = 0; i < 10; i+= 2)
Delete(i, list);
PrintList(list);
printf("\n===============test find :============================\n");
for(i = 0; i < 10; i++)
if((i % 2 == 0 ) == (Find(i, list) != NULL ) )
printf( "Find fails\n" );
printf("\n===============test last :============================\n");
last = Last(list);
printf("\nlast is %d\n", Retrieve(last));
printf("\n===============test insert last :============================\n");
InsertLast(10, list);
PrintList(list);
printf("\n===============test delete list :=====================\n");
DeleteList(list);
if(list == NULL)
printf("L is null\n");
PrintList(list);
printf("\n===============test last again:========================\n");
last = Last(list);
printf("\nag last is %d\n", Retrieve(last));
printf("\n===============test insert last again:=================\n");
InsertLast(100, list);
PrintList(list);
return 0;
}
2-4 反思一下
- 检索操作很耗时,在末尾插入也是,考虑升级实现方法,添加一个尾结点。
- 链表的翻转这个经典的操作还需实现
PS:发现很多人直接盗用刘大佬的PPT资料和代码,却不给注明转载或参考,呵呵。
下一步我们将继续改进self版本链表实现。
后记:可能有人会吐槽为何代码写的如此繁琐,其实吧我是参考
《数据结构与算法分析:C语言描述》(原书第2版)中的模式去写的,对于初学者来讲是很容易理解的,过一段时间,我会给出面试用的简写版本,方便记(背)忆(书)。