先来比较一下顺序表跟单链表的优缺点:
顺序表:
优点: 可随机存取,存取密度高
缺点:要求大片连续空间,改变容量不方便
单链表:
优点:不要求大量的存储空间,改变容量方便
缺点:不可随机存取,要耗费一定空间存放指针
单链表
用线性存储的方式实现线性结构。
结点:空间+指针
定义一个单链表
∵单链表都是靠其结构中的指针找寻下一个结点的头指针而连起来的,∴找到这个单链表只需要找到其头指针即可。
1 2 3 4 5 6 7 8 9
| typedef struct LNode { ElemType data; struct LNode* next; }LNode,*LinkList; LinkList L;
struct LNode* p = (struct LNode*)malloc(sizeof(struct LNode));
|
LinkList等价于LNode*
前者强调这个是单链表,后者强调这是结点
区分来写为了提高代码可读性。
初始化
不带头节点
1 2 3 4 5 6 7 8 9 10 11 12 13
| bool InitList(LinList &L){ L=NULL; return true; }
bool Empty(LinkList L){ return (L==NULL); } void test(){ LinkList L; InitList(L); }
|
带头节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| typedef struct LNode { ElemType data; struct LNode* next; }LNode,*LinkList; bool InitList(LinList &L){ L= (LNode*)malloc(sizeof(LNode)); if(L==NULL) return false; L->next=NULL; return true; } bool isEmpty(LinList L){ return L->next==NULL; } void test(){ LinkList L; InitList(L); }
|
带头节点先办会比不带头节点更方便。
插入
LineInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素。
带头节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| typedef struct LNode { int data; struct LNode* next; }LNode, *LinkList;
bool ListInert(LinkList &L,int i,ElemType e){ if(i<1) return false; LNode *p; int j =0; p =L; while(p!=NULL && j<i-1){ p=p->next; j++; } if(p==NULL) return false; LNode *s=(LNode *)malloc(sizeof(LNode)); s->data=e; s->next=p->next; p->next=s; return true; }
|
平均时间复杂度=O(n)
不带头结点
如果不带头结点,则插入、删除第1个元素时,需要更改头指针L。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| typedef struct LNode { int data; struct LNode* next; }LNode, *LinkList;
bool ListInert(LinkList &L,int i,ElemType e){ if(i<1) return false; if(i==1){ LNode *s=(LNode *)malloc(sizeof(LNode)); s->data=e; s->next=L; L=s; return true; } LNode *p; int j =1; p =L; while(p!=NULL && j<i-1){ p=p->next; j++; } if(p==NULL) return false; LNode *s=(LNode *)malloc(sizeof(LNode)); s->data=e; s->next=p->next; p->next=s; return true; }
|
指定结点的后插操作
在指定结点之前插入都是未知区域,之后都是可知区域
1 2 3 4 5 6 7 8 9 10 11 12
| bool InsertNextNode(LNode *p,ElemType e){ if(p==NULL) return false; LNode *s=(LNode *)malloc(sizeof(LNode)); if(s==NULL) return false; s->data=e; s->next=p->next; p->next=s; return true; }
|
时间复杂度=O(1)
对照上面的带头节点的第i个元素插入元素e可直接找到第i-1个结点,调用此时的后插操作。
指定结点的前插操作
直接去找前驱除非指定头结点,否则很难办到
这时可以将要插入的结点与该结点替换
1 2 3 4 5 6 7 8 9 10 11 12 13
| bool InsertPriorNode(LNode *p,ElemType e){ if(p==NULL) return false; LNode *s=(LNode *)malloc(sizeof(LNode)); if(s==NULL) return false; s->next=p->next; p->next=s; s->data=p->data; p->data=e; return true; }
|
or
1 2 3 4 5 6 7 8 9 10
| bool InsertPriorNode(LNode *p,LNode *s){ if(p==NULL||s=NULL) return false; s->next=p->next; p->next=s;//将新结点s连接到p之后 ElemType temp=->p->data;//交换数据部分 p->data=s->data;//将p中元素复制到s中 s->data=temp;//p中元素覆盖为e return true; }
|
删除
ListDelete(&L,i,&e):删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
找到第i-1个结点,将其指针指向第i+1个结点,并释放第i个结点。
带头指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| typedef struct LNode { int data; struct LNode* next; }LNode, *LinkList;
bool ListDelete(LinkList &L,int i,ElemType &e){ if(i<1) return false; LNode *p; int j =0; p =L; while(p!=NULL && j<i-1){ p=p->next; j++; } if(p==NULL) return false; if(p->next==NULL){ return false } LNode *q=p—>next; e=q->data; p->next=q->next; free(q); return true; }
|
最坏、平均时间复杂度=O(n)
最好时间复杂度=O(1)
指定结点的删除
1 2 3 4 5 6 7 8 9
| bool DeleteNode (LNode *p) { if (p==NULL) return false; LNode *q=p->next; p->data=p->next->data; p->next=q->next; free(q); return true; }
|
如果要被删除的结点恰巧是最后一个结点,p指向next的后继的时候会出现bug
此时只能从头开始检索
时间复杂度=O(1)
查找
按位查找
GetElem(L,i):按位查找操作,获取表中第i个位置的元素的值。
1 2 3 4 5 6 7 8 9 10 11 12
| LNode * GetElem(LinkList L,int i){ if(i<0) return false; LNode *p; int j =0; p =L; while(p!=NULL && j<i){ p=p->next; j++; } return p; }
|
平均时间复杂度=O(n)
则带头结点的插入操作也可简化
1 2 3 4 5 6
| bool ListInert(LinkList &L,int i,ElemType e){ if(i<1) return false; LNode *p=GetElem(L,i-1); return InsertNextNode(p,e); }
|
按值查找
LocateElem(L,e):按值查找操作,在表L中查找具有给定关键字值的元素。
1 2 3 4 5 6 7 8
| LNode * LocateElem(LinkList L,ElemType e){ LNode *p =L->next; while(p!=NULL && p->data!=e) p=p->next; return p; }
|
时间复杂度为O(n)
求表的长度
1 2 3 4 5 6 7 8 9
| int Length(LinkList L){ int len=0; LNode *p=L; while(p->next !=NULL){ p=p->next; len++; } return len; }
|
时间复杂度为O(n)
单链表的建立
尾插法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| LinkList List_TailInsert(ListList &L){ int x; L=(LinkList)malloc(sizeof(LNode)); LNode *s,*r=L; scanf("%d",&x); while(x!=8848){ s=(LNode*)malloc(sizeof(LNode)); s->data=x; r->next=s; r=s; scanf("%d",&x); } r->next=NULL; return L; }
|
头插法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| LinkList List_HeadInsert(ListList &L){ LNode *s; int x; L=(LinkList)malloc(sizeof(LNode)); L->next=NULL; scanf("%d",&x); while(x!=8848){ s=(LNode*)malloc(sizeof(LNode)); s->data=x; r->next=L->next; L->next=s; scanf("%d",&x); } return L; }
|
因为每次直接往头结点后面插入
这样会使得插入的数据逆置
小结
饿🍲