線性表的鏈式存儲與運算實現
1 先修知識
1.1 malloc與new的區別
new會自動根據類型分配內存地址,並返回同類型的指針給引用
int *p1,*p2;
p1 = new int;
p2 = new int[100];
malloc只能動態分配內存,意思就是不會自動分配,必須人爲設定內存大小與返回的指針類型
int *p;
p = (int*)malloc(sizeof(int)*100);
1.2 ->的用法
->是間接引用符,是二目訪問符,類似於成員符 .
typedef struct Node{
int a;
double b;
}node, *linkList;
*linkList = &node;
//struct Node *p = &node;
//此時
linkList->a等價於(*linkList).a;
1.3 struct的用法
自定義類型struct
typedef struct Node{
Elemtype a;
Node *next;
}node,linkList; //結構變量node等有點像靜態類,這裏借用java編程思想描述,Node是父類,node,linkList是子類.
或
struct Node node,linkList;//按模板類型Node定義具體類型node,linkList
寫一個struct Node,編譯器只是讀取它,並不會爲它賦予內存空間.邏輯合理,它只是一個模板.
2 節點
節點由數據域和指針域構成.指針域決定先後鏈接順序,有時鏈接關係不好修改的,可以直接交換數據域的內容,間接達到交換節點的目的.
單鏈表由一個個節點構成
typedef struct node{
datatype data;
struct node *next;
}LNode
node LNode = {};
等價於
public class node{
Object data;
class node next;
}
public class LNode extends node{}
3 棧
後進先出,前插法
抽象:表L; 新節點s(malloc,s->a=x); s->next=L(指向表頭);L=s(表頭指向s,s再指向next裏保存的舊錶頭地址)
linkList create_stack(int flag){
linkList L = null; //linkList是指針子類,故L也是指針,又L=null靜態聲明,省去了malloc分配.
// 同時! L==null;第一個節點s->next=L,巧妙地使最後一個節點的next爲null.不會出現野指針.
node *s;
int x;
scanf("%d",&x);
int cout=1;
while(cout<=flag){
s=(Node*)malloc(sizeof(node));//每次循環都會手動new一個s.因爲新的s節點的next後繼是上一節點,所以新的s節點是上一節點的前驅.即新的s節點是前插的.滿足棧後進先出的形式.
s->data=x;
s->next=L;//s上鍊指向L的表頭,此時s還不算在L內
L = s;//讓s做L的表頭,即巧妙地讓L永遠指向新的表頭元素.
scanf("%d",&x);
cout++;
}
return L;
}
4 隊列
後進後出,尾插法
表L只要指向表頭就夠了,我們可以用副表指針*r先指向第一個節點(s作表頭時),它與L都是指向第一個節點的引用.
linkList create_quene(int flag){
linkList L=null;
node *s,*r=null;
int x;
scanf("%d",&x);
int cout=1;
while(cout<=flag){
s = malloc(sizeof(node)); //new一個s,new完對s的屬性賦值
s->a = x;
if(L=null) L=s;
else r->next = s;上鍊
r = s; //此處r,L都是s的軟引用.L佔了r的光,之後一條龍的鏈表全靠r接上.L最多指向表頭,其餘上鍊操作有r來完成
scanf("%d",&x);
cout++;
}
//定義表尾節點的後繼節點爲空.
}
5 利用表指針求表長
表指針順節點往下走(賦值),表指針都是數據域爲空,指針域指向當前節點,與節點同類型的內存空間.
後插法裏的L,*r都是表指針,其中L作頭指針變量,它所引用的節點叫頭節點.即L是頭節點,L->next纔是第一個數據節點
int linkList_length(linkList L){
node *p = L;
int j=0;
while(p->next!=nuLl){
p=p->next;
j++;
}
return j;
}
6 前插法與後插法
*p指向某個節點,*s指向待插入的新節點
graph LR
後插法
A(節點p)-->|起初令p->next=p',得到|B(p的下一節點是p')
A-->|最終p的下一節點是s|C
C-->|第二步p->next=s|A
C(待插入節點s)-->|第一步s->next=p`|B
graph BT
前插法
B(當前節點p)-->|第一步 q=L, while \q->next!=p\ q=q->next|A(找到p的前驅q)
C-->|第二步 s->next=p|B
A-->|第三步 q->next=s|C
C(待插入節點s)
graph TB
後插+交換實現前插
A(節點p)-->|起初 p->next=p`|B(p的下一節點是p`)
A-->|交換節點數據swap\p->next,s->next\|C
C-->|第二步p->next=s|A
C(待插入節點s)-->|第一步s->next=p`|B
7 刪除節點
當前節點p,前驅節點q
- 已知p,找到它的前驅q
- *q->next = *p->next
- free§
8 刪除當前節點的後一節點
當前節點*p
- node *s;
- s = p->next;
- p->next = s->next;
- free(s);
9 刪除表L的第i個元素
- for循環找到L[i-1]
- 若L[i-1]->next != null
- 則用刪除節點法,刪除第i個節點
10 單循環列表
- 尾指針指向頭指針
- 判斷表已經遍歷一遍的方法是p->next == head,是則已到尾指針.
- 有時可用r指向尾指針,直接從尾部開始遍歷,加快運算.
11 單鏈表總結
單鏈表不具備按序號隨機(直接)訪問元素的能力,必須先找到它的前驅節點,才能訪問要訪問的節點.即只能從頭指針開始訪問.
12 雙向鏈表
prior | data | next |
---|
typedef struct dlnode{
datatype data;
struct dlnode *prior,*next;
}DLnode,*DLinkList
單鏈表能做的事它都能做,最大的區別就是每修改一次鏈表,必須前後節點都要定義指向一次
12.1 插入
已知p,插入s
s->prior = p->prior;
p->prior->next = s;
p->prior = s;
s->next = p;
12.2 刪除(free)
已知p,刪除p
- p->prior->next = p->next;
- p->next->prior = p->prior;
- free§;
1.6 以a1爲基準,劃分線性表
劃分就是重排列,比a1小的在前,大的在後
1.7 兩線性表比較大小
A長a,B長b,兩數組第一次不同時的下標i,A的剩餘長度as,B的剩餘長度bs,
- for循環使下標走到兩數組第一次不同的地方,i
- for(j=i;j<a;j++){ AS[j-i]=A[j];a++;}
- for(j=i;j<b;j++){ BS[j-i]=B[j];b++;}
- if(asbs&&as0) return 0;//A==B
- if(as==0&&bs>0 || as>0&&bs>0&&AS[0]<BS[0]) return -1;//A<B
- else return 1;//A>B
1.8 倒轉單鏈表
將原鏈表H中的每一個元素依次作爲第一個元素插入到新鏈表中
void reverse(LinkList H){
Node *p;
p=H->next;
while§{
q=p;
p=p->next;
//插入q到頭節點後面
q->next = H->next;
H->next = q;
}
}
刪除表L中重複的元素
從首元素開始,遍歷表L,刪除與首元素相同的元素(修改鏈接關係),類推
兩層遍歷,兩層while
void pur_LinkList(LinkList H){
Node *p,*q,*r;
p=H->next;
while(p->next){
q=p;
while(q->next){
if(q->next->data=p->next){
r = q->next;
q->next=r->next;
free(r);
}else{
q=q->next;//下一個對比元素
}
p=p->next;//下一個首元素
}
}
按訪問權比排序鏈表元素
prior前驅指針域,data數據域,next後繼指針域,freq權比域