線性表的基本概念
線性表的定義
線性表是由\(n(n>=0)\) 個相同類型的數據元素組成的有限序列,標記為:
\(L = (a_{1},a_{2},...,a_{i},...,a_{n})\)
- 線性表中元素的個數n定義為線性表的長度,當\(n=0\)時為空表。
- 當\(n>0\)時,線性表的邏輯結構如圖所示:
線性表的幾個概念:
邏輯特征:
- 若至少含有一個元素,則只有唯一的起始元素
- 若至少含有一個元素,則只有唯一的尾元素
- 除了起始元素外,其他元素有且僅有一個前驅元素
- 除了尾元素外,其余元素有且只有一個后繼元素
線性表中的每個元素有唯一的序號(邏輯序號),同一個線性表中可以有多個相同的元素,但他們的序號是不同的。
線性表的基本運算
1、初始化InitList(L)。其作用是建立一個空表L(即建立線性表的構架,但不含任何數據元素)。
2、銷毀線性表DestroyList(L)。其作用是釋放線性表L的內存空間。
3、求線性表的長度ListLength(L)。其作用是返回線性表L的長度。
4、求線性表中第i個元素GetElem(L,i,e)。其作用是返回線性表L的第i個數據元素。
5、按值查找LocateElem(L,x)。若L中存在一個或多個值與x相等的元素,則其作用是返回第一個值為x的元素的邏輯序號。
6、插入元素ListInsert(L,i,x)。其作用是在線性表L的第i個位置上增加一個以x為值的新元素
7、刪除元素ListDelete(L,i)。其作用是刪除線性表L的第i個元素ai。
8、輸出元素值DispList(L)。其作用是按前后次序輸出線性表L的所有元素值
我們通過以下實例進行分析:
1、利用兩個線性表LA,LB分別表示兩個集合\(A\&B\),要求一個新集合\(A=A∪B\)
void Union(SqList &La,SqList Lb)
{
ElemType e;
int la_len,lb_len;
int i;
la_len = ListLength(La)//ListLength 返回長度
lb_len = ListLength(Lb)//ListLength 返回長度
for(i = 1;i <= lb_len ;i++)
{
GetElem(Lb,i,e)//取Lb中第i個數據元素賦給e
if(!LocateElem(La,e,equal))
ListInsert(La,++la_len,e);//// La中不存在和e相同的元素,則插入之
}
}
線性表LA和LB,其元素均按非遞減有序排列,編寫一個算法將它們合並成一個線性表LC,且LC的元素也是按非遞減有序排列。
- 思路:掃描La,Lb的元素,比較當前元素的值,將較小的元素值賦給Lc,最后在處理沒有插入的值
void MergeList(SqList La,SqList Lb,SqList &Lc)
{
int i = 1,j = 1, k = 0;
int la_len = ListLength(La),lb_len = ListLength(Lb);
ElemType ai,bj;
InitList(Lc);
//比較大小
while(i<=la_len&&j<lb_len)
{
GetElem(La,i,ai);
GetElem(Lb,j,bj);
if(ai<bj)
ListInsert(Lc,++k,ai),i++;
else
ListInsert(Lc,++k,bj),j++;
}
//處理La剩余
while(i<=la_len)
{
GetElem(La,i++,ai);
ListInsert(Lc,++k,ai);
}
//處理Lb剩余
while(j<=lb_len)
{
GetElem(Lb,j++,bj);
ListInsert(Lc,++k,bj);
}
}
線性表的順序存儲和實現
順序表的定義
-
順序表的定義和特點
定義:將將線性表中的元素相繼存放在一個連續的存儲空間中,即構成順序表。
存儲:它是線性表的順序存儲表示,可利用一維數組描述存儲結構。
特點:元素的邏輯順序與物理順序一致。
訪問方式:可順序存取,可按下標直接存取。 -
順序表的連續存儲方式
\(LOC(i) = LOC(i-1) + l = a + i * l,LOC是元素存儲位置,l是元素大小\)
在介紹存儲結構之前,我們先給出相應的類型重定向:
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
typedef int Status; // Status值是函數結果狀態代碼,如OK等
假定線性表的數據元素的類型為ElemType,類型聲明如下:
// ------ 線性表的動態分配順序存儲結構 --------
#define LIST_INIT_SIZE 100 // 線性表存儲空間的初始分配量
#define LIST_INCREMENT 10 // 線性表存儲空間的分配增量
typedef int ElemType; //元素的數據類型
typedef struct {
ElemType *elem; // 存儲空間基址
int length; // 當前長度
int listsize; // 當前分配的存儲容量
} SqList;
同樣的,靜態存儲結構類型聲明如下:
// ------ 線性表的靜態分配順序存儲結構 --------
#define MaxSize 100
typedef int ElemType; //假設順序表中所有元素為int類型
typedef struct
{
ElemType data[MaxSize]; //存放順序表的元素
int length; //順序表的實際長度
} SqList; //順序表類型
- 由於順序表采用數組存放元素,數組具有隨機存取特性,所以順序表具有隨機存取特性。
基本運算實現
- 初始化線性表算法
Status InitList_Sq(List &L)
{
L.elem=(ElemType*)malloc(LIST_INIT_SIZE*sizeof(ElemType));
if(!L.elem)return ERROR; //健壯性,不過一般不會失敗
L.length = 0; //長度置為0
L.listsize = LIST_INIT_SIZE; //最大長度
return OK;
}
- 銷毀線性表算法
Status DestroyList(SqList &L)
{
free(L.elem);
L.elem = NULL; //指針置空,防止野指針
L.length = 0;
L.listsize = 0;
return OK;
}
- 求線性表長度
int GetLength(SqList L)
{
return L.length;
}
- 求第i個元素
Status GetElem(SqList L, int i, ElemType &e)
{
if(i<1||i>L.length)
return ERROR; //后面可以改為:i>Getlength(L);
e = *(L.elem + i -1); //公式,隨機存取
return OK;
}
- 按值查找
在順序表L找第一個值為e的元素,找到后返回其邏輯序號,否則返回0(由於線性表的邏輯序號從1開始,這里用0表示沒有找到值為e的元素)。
int LocateElem(SqList L, ElemType e)
{
ElemType *p;
int i = 1;
p = L.elem;
while(i<=L.length && (*p++ != e))//核心代碼
++i;
if(i<=L.length)
return i;
else
return 0;
}
- 插入算法
將新元素e插入到順序表L中邏輯序號為i的位置(如果插入成功,元素e成為線性表的第i個元素)。
i的合法值為1≤i≤L.Length+1。當i無效時返回0(表示插入失敗)。
有效時將L.elem[i-1..L.length-1]后移一個位置, 在L.elem[i-1]處插入e,順序表長度增1,並返回1 (表示插入成功)
Status ListInsert_Sq(SqList &L, int i, ElemType e)
{
ElemType *p;
if(i < 1 || i > L.length + 1)
return ERROR;
//這里不再考慮內存不足再使用relloc申請空間的情況
ElemType *q = &(L.elem[i-1]); //q為插入位置
for(p = &(L.elem[L.length - 1]);p>=q;--p)
*(p+1) = *p; //插入位置及之后的元素右移
*q = e;
++L.length; //表長+1
return OK;
}
- 刪除算法
刪除順序表L中邏輯序號為i的元素
i的合法值為1≤i≤L.Length。在i無效時返回0 (表示刪除失敗)。
有效時將L.elem[i..length-1]前移一個位置,順序表長度減1,並返回1(表示刪除成功)
Status ListDelete_Sq(SqList &L, int i, ElemType &e)
{
ElemType *p,*q;
if(i<1 || i>L.length)//位置不合法
return ERROR;
p = &(L.elem[i-1]);//p 為刪除元素的位置
e = *p; //存儲其值
q = L.elem +L.length - 1; //表尾元素的位置
for(++p;p<=q;p++) //從下一個位置開始,逐步覆蓋
*(p-1) = *p; //元素左移
--L.length;
return OK;
}
拓展實例
- 假設有一個順序表L,其中元素為整數且所有元素值均不相同。設計一個算法將最大值元素與最小值元素交換。
用maxi和mini記錄順序表L中最大值元素和最小值元素的下標,初始時maxi=mini=0。
i從1開始掃描所有元素:當L.elem[i]>L.elem[maxi]時置maxi=i;否則若L.elem[i]<L.elem[mini]時置mini=i。
掃描完畢時,L.elem[maxi]為最大值元素,L.elem[mini]為最小值元素,將它們交換。
void SwapMaxMin(SqList &L)
{
int i,maxi,mini;
maxi = mini = 0; //存儲的是下標
for(i = 1;i<L.length;i++)
if(L.elem[i]>L.elem[maxi])
maxi = i;
else if(L.elem[i]<L.elem[mini])
mini = i;
swap(L.elem[maxi],L.elem[mini]);//調用庫函數即可
}
- 從線性表中刪除自第i個元素開始的k個元素,其中線性表用順序表L存儲。
將線性表中a_{i}~a_{i+k-1}元素(對應\(L.elem[i-1..i+k-2]\)的元素)刪除,即將\(a_{i}+k~a_{n}\)(對應\(L.elem[i+k-1..n-1]\))的所有元素依次前移k個位置。
int Deletek(SqList &L,int i,int k)
{
int j;
if(i<1||k<1||i+k-1>L.length)
return 0;
for(j = i+k-1;j<L.length;j++) //元素前移k個位置
L.elem[j-k]=L.elem[j]; //i+k-1是不被刪除的第一個元素,j-k是被刪的第一個元素
L.length -= k; //長度減k
return 1;
}
- 已知線性表(a1,a2,…,an)采用順序表L存儲,且每個元素都是互不相等的整數。設計一個將所有奇數移到所有的偶數前邊的算法(要求時間最少,輔助空間最少)
並沒有要求有大小次序
置i=0,j=n-1,在順序表L中從左向右找到偶數L.elem[i],從右向左找到奇數L.elem[j],將兩者交換;循環這個過程直到i等於j為止。
void Move(SqList &L)
{
int i = 0,j = L.length - 1;
while(i<j)
{
while(L.elem[i]%2==1)i++; //從前向后找偶數
while(L.elem[j]%2==0)j--; //從后向前找奇數
if(i<j)swap(L.elem[i],L.elem[j]); //交換
}
}
- 已知一個整數線性表采用順序表L存儲。設計一個盡可能高效的算法刪除其中所有值為負整數的元素(假設L中值為負整數的元素可能有多個)。
每次刪除都需要移動大量元素,會消耗大量的時間,可以換一個角度考慮
采用整體重建順序表的算法思路,僅僅將插入元素的條件設置為“元素值≥0”即可。
void De(SqList &L)
{
int i,k = 0;
for(i = 0;i < L.length;i++)
{
if(L.elem[i]>=0) //非負數插入
{
L.elem[k] = L.elem[i];
k++;
}
}
L.length = k; //重置長度
}
- 設將\(n(n>1)\)個整數存放到一維數組R中。試設計一個時間和空間兩方面盡可能高效的算法,將R中整數序列循環左移\(p(0<p<n)\)個位置,即將R中的數據序列\((X_{0},X_{1},…,X_{n-1})\)變換為\((X_{p},X_{p+1},…,X_{n-1},X_{0},X_{1},…,X_{p-1})\)
設\(R=(X_{0},X_{1},…,X_{p},X_{p+1}, …,X_{n-1)}\),記\(A=(X_{0},X_{1},…,X_{p-1})\) (共有p個元素),\(B=(X_{p},…,X_{n-1})\)(共有n-p個元素)
設計逆置算法reverse(R),用於原地逆置數組R,則A原地逆置后A’變為\((X_{p-1},…,X_{1},X_{0})\),B原地逆置后B’變為\((X_{n-1},…,X_{p-1},X_{p})\)
就是說\(A’B’=(X_{p-1},…,X_{1},X_{0},X_{n-1},…,X_{p-1},X_{p})\),再將A’B’原地逆置變為\((X_{p},X_{p-1},…,X_{n-1},X_{0},X_{1},…,X_{p-1})\)即為所求,即進行3次逆置操作:\(reverse(R)=reverse(reverse(A),reverse(B))\)
void reverse(int R[],int m,int n) //將R[m..n]逆置
{
int i,tmp;
for (i=0;i<(n-m+1)/2;i++)
{
tmp=R[m+i]; //將R[m+i]與R[n-i]進行交換
R[m+i]=R[n-i];
R[n-i]=tmp;
}
}
int ListReverse(int R[],int n,int p) //循環左移
{
if (p<=0 || p>=n)
return 0;
else
{
reverse(R,0,p-1);
reverse(R,p,n-1);
reverse(R,0,n-1);
return 1;
}
}
線性表的鏈式存儲和實現
線性鏈表
存儲方式
線性表的單鏈表存儲方法:用一個指針表示結點間的邏輯關系。
因此單鏈表的一個存儲結點包含兩個部分,結點的形式如下:
data:為數據域,用於存儲線性表的一個數據元素,也就是說在單鏈表中一個結點存放一個數據元素。
next:為指針域或鏈域,用於存放一個指針,該指針指向后繼元素對應的結點,也就是說單鏈表中結點的指針用於表示后繼關系。
- 特點
每個數據元素由結點構成
線性結構
結點可以連續,可以不連續存儲。
結點的邏輯順序與物理順序可以不一致
表可擴充。
鏈表中第一個元素結點稱為首結點;最后一個元素稱為尾結點,其指針域(next)為空(NULL)。
- 結點類型聲明
typedef int ElemType;
typedef int Status;
#define OK 1
#define ERROR 0
// ------ 結點的C語言描述 --------
typedef struct node {
ElemType data;
struct node *next;
} LNode, *LinkList;
帶頭節點的單鏈表示意圖
常規算法設計
- 初始化單鏈表算法
創建一個空的單鏈表,它只有一個頭結點,由L指向它。該結點的next域為空,data域未設定任何值。
Status InitList(LinkList &L)
{
L = (LinkList)malloc(sizeof(LNode));
if(!L)
return ERROR;
L->next = NULL; //指針域為空
return OK;
}
- 銷毀單鏈表算法
一個單鏈表中的所有結點空間都是通過malloc函數分配的,在不再需要時需通過free函數釋放所有結點的空間
借助一個遍歷指針
Status DestroyList(LinkList &L)
{
LinkList p; //臨時指針
while(L)
{
p = L->next
free(L);
L = p;
}
return OK;
}
- 求單鏈表長度算法
設置一個整型變量i作為計數器,i初值為0,p初始時指向第一個數據結點。
然后沿next域逐個往后查找,每移動一次,i值增1。當p所指結點為空時,結束這個過程,i之值即為表長。
int ListLength(LinkList L)
{
int i = 0;//計數器
LinkList p = L->next; // p指向第一個結點
while(p) { // 沒到表尾
i++;
p = p->next;
}
return i;
}
- 求單鏈表中值為e的元素算法
用p從頭開始遍歷單鏈表L中的結點,用計數器i統計遍歷過的結點,其初值為0。
在遍歷時,若p不為空,則p所指結點即為要找的結點,查找成功,算法返回位序i
否則算法返回0表示未找到這樣的結點。
int LocateElem(LinkList L, ElemType e)
{
int i = 0;
LinkList p = L->next;
while(p)
{
i++;
if(p->data == e)
return i;
p = p->next;
}
return 0;
}
- 單鏈表的插入算法
在單鏈表L中第i個位置,插入值為x的結點。
找前驅結點
先在單鏈表L中查找第i-1個結點,若未找到返回0;
找到后由p指向該結點,創建一個以x為值的新結點s,將其插入到p指結點之后。
在p結點之后插入s結點的操作如下:
① 將結點s的next域指向p的下一個結點(s->next=p->next)。
② 將結點p的next域改為指向新結點s(p->next=s)。
插入操作的①和②執行順序不能顛倒。
記憶方法:先把沒有指向的指針指向一個位置,進行操作
Status ListInsert_L(LinkList &L, int i, ElemType e)
{
LinkList p, s;
p = L;
int j = 0;
while(p && j<i-1)p = p->next,j++; //找結點
if(!p||j>i-1) return ERROR;//位置不合法
s = (LinkList)malloc(sizeof(LNode)); // 生成新結點
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
- 單鏈表的刪除算法
先在單鏈表L中查找第i-1個結點,若未找到返回0
找到后由p指向該結點,然后讓q指向后繼結點(即要刪除的結點)。
若q所指結點為空則返回0,否則刪除q結點並釋放其占用的空間。
刪除p指結點的后繼結點的過程:p->next=q->next;
free(q);
即需要找到刪除結點的前驅結點
Status ListDelete_L(LinkList &L, int i, ElemType &e)
{
LinkList p, q;
p = L;
int j = 0;
while(p && j<i-1)p = p->next,j++; //找結點
if(!(p->next)||j>i-1) return ERROR;//位置不合法
q = p->next; //p為前驅,q為當前結點
e = q->data;
free(q);
return OK;
}
拓展實例
- 設計一個算法,通過一趟遍歷確定單鏈表L(至少含兩個數據結點)中第一個元素值最大的結點。
用p遍歷單鏈表,在遍歷時用maxp指向data域值最大的結點(maxp的初值為p)。當單鏈表遍歷完畢,最后返回maxp。
LNode *MaxNode(LNode *L)
{
LNode *p=L->next, *maxp=p;
while (p!=NULL) //遍歷所有的結點
{
if(maxp->data<p->data)
maxp = p; //當p指向的值大於maxp時,更新maxp
}
return maxp;
}
- 刪除一個單鏈表L(至少含兩個數據結點)中第一個元素值最大的結點。
以p遍歷單鏈表,pre指向p結點的前驅結點。
在遍歷時用maxp指向data域值最大的結點,maxpre指向maxp結點的前驅結點。
當單鏈表遍歷完畢,通過maxpre結點刪除其后的結點,即刪除了元素值最大的結點。
用到了同步指針
void DelMaxNode(LNode *&L)
{
LNode *p=L->next,*pre=L,*maxp=p,*maxpre=pre; //初始化
while(p!=NULL)
{
if(maxp->data < p->data)
{
maxp = p;
maxpre = pre;
}
pre = p;
p = p->next;//pre、p同步后移,保證pre始終為p的前驅結點
}
maxpre->next = maxp->next;//刪除maxp結點
free(maxp);//釋放maxp結點
}
- 將一個單鏈表L(至少含兩個數據結點)中所有結點逆置,並分析算法的時間復雜度。
先將單鏈表L拆分成兩部分,一部分是只有頭結點L的空表,另一部分是由p指向第一個數據結點的單鏈表。
然后遍歷p,將p所指結點逐一采用頭(前)插法插入到L單鏈表中,由於頭插法的特點是建成的單鏈表結點次序與插入次序正好相反,從而達到結點逆置的目的。
頭插法天然可以實現單鏈表逆置
一樣,還是先處理沒有指向的指針,再做其他處理
void ListReverse(LNode *&L)
{
LNode *p = L->next,*q;
L->next = NULL;
while(p!=NULL)
{
q = p->next;
p->next = L->next;//頭插法
L->next = p;
p = q;
}
}
- 假設該單鏈表只給出了頭指針list。在不改變鏈表的前提下,請設計一個盡可能高效的算法,查找鏈表中倒數第k個位置上的結點(k為正整數)。若查找成功,算法輸出該結點的data域的值,並返回1;否則,只返回0。
單鏈表只能從前往后遍歷,並不能從后往前,所以想要再一次循環內找到相對應的結點,只能采用特殊的方式。
考慮雙指針的思路,定義兩個指針變量p和q,初始時均指向頭結點的下一個結點。p指針沿鏈表移動; 當p指針移動到第k個結點時,q指針開始與p指針同步移動;當p指針移動到鏈表尾結點時,q指針所指元素為倒數第k個結點。
詳細思路:
①置count=0,p和q指向鏈表表頭結點的下一個結點。
②若p為空,則轉向⑤ 。
③若count等於k,則q指向下一個結點;否則,置count=count+1。
④置p指向下一個結點,轉向② 。
⑤若count等於k,則查找成功,輸出該結點的data域的值,返回1;否則,查找失敗,返回0。
⑥算法結束。
int Searchk(LinkList list,int k)
{
LinkList p, q;
int count = 0;
p = q = list->link;
while (p!=NULL && count<k) //查找第k個結點*p
{
count++;
p=p->link;
}
if(p == NULL)
return 0;
else
{
while(p!=NULL)//沒有時返回0
{
q = p->link;//p和q同步后移直到p=NULL
p = p->link;
}
printf("%d",q->data);
return 1;
}
}
循環鏈表
循環鏈表是單鏈表的變形,鏈表尾結點的next指針不是NULL,而是指向了單鏈表的前端。
循環鏈表的判空條件:L->next == L;
循環單鏈表的特點是:只要知道表中某一結點的地址,就可搜尋到所有其他結點的地址。
在搜尋過程中,沒有一個結點的 next 域為空遍歷條件:for ( p = L->next; p != L; p = p->next )
循環單鏈表的所有操作的實現類似於單鏈表,差別在於檢測到鏈尾
,指針不為NULL,而是回到鏈頭
。
算法設計
- 初始化線性表運算算法
創建一個空的循環單鏈表,它只有頭結點,由L指向它。
Status InitList_cl(LinkList &L)
{
L = (LinkList)malloc(sizeof(LNode));
if(!L)exit(OVERFLOW);
L->next = L;
return OK;
}
- 求線性表的長度算法
int ListLength(LinkList L)
{
int i = 0;
LinkList p = L->next; // p指向第一個結點
while(p != L) { // 沒到表尾
i++;
p=p->next;
}
return i;
}
其他
- 設計一個算法求一個循環單鏈表L中所有值為x的結點個數
int CountNode(LNode *L, ElemType x)
{
int i = 0;
LNode *p = L->next;
while(p!=L)
{
if(p->data == x)i++;
p = p->next;
}
return i;
}
- 有一個非遞減有序的循環單鏈表L,設計一個算法刪除其中所有值為x的結點
由於循環單鏈表L是非遞減有序的,則所有值為x的結點必然是相鄰的
先找到第一個值為x的結點p,讓pre指向其前驅結點
然后通過pre結點刪除p結點及其后面連續值為x的結點
int Delallx(LNode *&L, ElemType x)
{
LNode *pre=L,*p=L->next; //pre指向p結點的前驅結點
while (p!=L && p->data!=x) //找第一個值為x的結點p
{
pre = p;
p = p->next;
}
if(p==L)return 0;//沒有找到值為x的結點返回0
while(p!=L&&p->data == x)//刪除所有值為x的結點
{
pre->next = p->next;
free(p);
p = pre->next;
}
return 1;//成功刪除返回1
}
雙向鏈表
雙向鏈表是指在前趨和后繼方向都能遍歷的線性鏈表。
雙向鏈表每個結點的結構為:
雙向鏈表中用兩個指針表示結點間的邏輯關系
指向其前驅結點的指針域prior。
指向其后繼結點的指針域next。
結點指向:
指向其后繼結點的指針域next。
p->next 指示結點 p 的后繼結點;
p->prior->next指示結點 p 的前趨結點的后繼結點,即結點 p 本身;
p->next->prior指示結點 p 的后繼結點的前趨結點,即結點 p 本身。
p->prior->next == p == p->next->prior 固有特性
存儲聲明
typedef struct DuLNode {
ElemType data;
DuLNode *prior, *next;
} DuLNode, *DuLinkList;
雙向循環鏈表
- 雙向循環鏈表長度
int ListLength(DuLinkList L)
{
int i = 0;
DuLinkList p = L->next; // p指向第一個結點
while(p!=L)
{
i++;
p = p->next;
}
return i;
}
- 插入
① 將結點s的next域指向結點p的下一個結點( s->next=p->next)。
② 若p不是最后結點(若p是最后結點,只插入s作為尾結點),則 將p之后結點的prior域指向s(p->next->prior=s)。
將s的prior域指向p結點(s->prior=p)。
④ 將p的next域指向s(p->next=s)。
還是先將沒有指向的指針指向該指向的位置
在雙向循環鏈表中,可以通過一個結點找到其前驅結點,所以插入操作也可以改為:在雙鏈表中找到第i個結點p,然后在p結點之前插入新結點。
Status ListInsert(DuLinkList L, int i, ElemType e)
{
DuLinkList p, s;
if(i<1 || i>ListLength(L) + 1) //判斷位置
return ERROR;
p = GetElemP(L, i - 1); // 在L中確定第i個元素前驅的位置指針p
if(!p) return ERROR;
s = (DuLinkList)malloc(sizeof(DuLNode));
if(!s) exit(OVERFLOW);
s->data = e;
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;
return OK;
}
- 刪除結點運算算法
若p不是尾結點,則將其后繼結點的prior域指向pre結點(p->next->prior=pre)
② 將pre結點的next域改為指向p結點的后繼結點(pre-next=p->next)
Status ListDelete(DuLinkList L, int i, ElemType &e)
{
DuLinkList p;
if(i<1) return ERROR;
p = GetElemP(L,i)//確定第i個位置
if(!p) return ERROR;
e = p->data; //保存刪除元素
p -> prior -> next = p->next;
p -> next ->prior = p->prior;
free(p);
return OK;
}