ticoAg/ChineseCorpus-Kaggle-fanti huggingface dataset:
Status GetElem(SqList L,int i,ElemType *e)
{	/* 初始條件：順序線性表L已存在,1≤i≤ListLength(L)。操作結果：用e返回L中第i個數據元素的值 */
if(iL.length)
return ERROR;
*e=*(L.elem+i-1);
return OK;
int LocateElem(SqList L,ElemType e,Status(*compare)(ElemType,ElemType))
{	/* 初始條件：順序線性表L已存在，compare是數據元素判定函數(滿足為1，否則為0) */
/* 操作結果：返回L中第1個與e滿足關系compare的數據元素的位序。 */
/*           若這樣的數據元素不存在，則返回值為0。算法2.6 */
ElemType *p;
int i=1; /* i的初值為第1個元素的位序 */
p=L.elem; /* p的初值為第1個元素的存儲位置 */
while(iL.length)
return INFEASIBLE; /* 操作失敗 */
*pre_e=*--p;
Status NextElem(SqList L,ElemType cur_e,ElemType *next_e)
{	/* 初始條件：順序線性表L已存在 */
/* 操作結果：若cur_e是L的數據元素，且不是最后一個，則用next_e返回它的后繼， */
/*           否則操作失敗，next_e無定義 */
int i=1;
ElemType *p=L.elem;
while(i(*L).length+1) /* i值不合法 */
if((*L).length>=(*L).listsize) /* 當前存儲空間已滿,增加分配 */
newbase=(ElemType *)realloc((*L).elem,((*L).listsize+LIST_INCREMENT)*sizeof(ElemType));
if(!newbase)
(*L).elem=newbase; /* 新基址 */
(*L).listsize+=LIST_INCREMENT; /* 增加存儲容量 */
q=(*L).elem+i-1; /* q為插入位置 */
for(p=(*L).elem+(*L).length-1;p>=q;--p) /* 插入位置及之后的元素右移 */
*(p+1)=*p;
*q=e; /* 插入e */
++(*L).length; /* 表長增1 */
Status ListDelete(SqList *L,int i,ElemType *e) /* 算法2.5 */
{	/* 初始條件：順序線性表L已存在，1≤i≤ListLength(L) */
/* 操作結果：刪除L的第i個數據元素，并用e返回其值，L的長度減1 */
ElemType *p,*q;
if(i(*L).length) /* i值不合法 */
p=(*L).elem+i-1; /* p為被刪除元素的位置 */
*e=*p; /* 被刪除元素的值賦給e */
q=(*L).elem+(*L).length-1; /* 表尾元素的位置 */
for(++p;p
在電腦科學中，鏈表是一種常見的基礎數據結構，是一種線性表，但是并不會按線性的順序存儲數據，而是在每一個節點里存到下一個節點的指針。由于不必須按順序存儲，鏈表在插入的時候可以達到O(1)的復雜度，比另一種線性表順序表快得多，但是查找一個節點或者訪問特定編號的節點則需要O(n)的時間，而順序表相應的時間復雜度分別是O(logn)和O(1)。
使用鏈表結構可以克服數組鏈表需要預先知道數據大小的缺點，鏈表結構可以充分利用計算機內存空間，實現靈活的內存動態管理。但是鏈表失去了數組隨機讀取的優點，同時鏈表由于增加了結點的指針域，空間開銷比較大。
在計算機科學中，鏈表作為一種基礎的數據結構可以用來生成其它類型的數據結構。鏈表通常由一連串節點組成，每個節點包含任意的實例數據（data fields）和一或兩個用來指向上一個/或下一個節點的位置的鏈接（"links"）。鏈表最明顯的好處就是，常規數組排列關聯項目的方式可能不同于這些數據項目在記憶體或磁盤上順序，數據的存取往往要在不同的排列順序中轉換。而鏈表是一種自我指示數據類型，因為它包含指向另一個相同類型的數據的指針（鏈接）。鏈表允許插入和移除表上任意位置上的節點，但是不允許隨機存取。鏈表有很多種不同的類型：單向鏈表，雙向鏈表以及循環鏈表。
鏈表可以在多種編程語言中實現。像Lisp和Scheme這樣的語言的內建數據類型中就包含了鏈表的存取和操作。程序語言或面向對象語言，如C/C++和Java依靠易變工具來生成鏈表。
鏈表開發于1955-56，由當時所屬于蘭德公司的艾倫紐維爾（Allen Newell），克里夫肖（Cliff Shaw）和赫伯特西蒙（Herbert Simon）在他們編寫的信息處理語言（IPL）中做為原始數據類型所編寫。IPL被作者們用來開發幾種早期的人工智能程序，包括邏輯推理機，通用問題解算器和一個計算機象棋程序。
鏈表中最簡單的一種是單向鏈表，它包含兩個域，一個信息域和一個指針域。這個鏈接指向列表中的下一個節點，而最后一個節點則指向一個空值。
Image:Singly-linked-list.svg''一個單向鏈表包含兩個值: 當前節點的值和一個指向下一個節點的鏈接''
一個單向鏈表的節點被分成兩個部分。第一個部分保存或者顯示關于節點的信息，第二個部分存儲下一個節點的地址。單向鏈表只可向一個方向遍歷。
鏈表最基本的結構是在每個節點保存數據和到下一個節點的地址，在最后一個節點保存一個特殊的結束標記，另外在一個固定的位置保存指向第一個節點的指針，有的時候也會同時儲存指向最后一個節點的指針。一般查找一個節點的時候需要從第一個節點開始每次訪問下一個節點，一直訪問到需要的位置。但是也可以提前把一個節點的位置另外保存起來，然后直接訪問。當然如果只是訪問數據就沒必要了，不如在鏈表上儲存指向實際數據的指針。這樣一般是為了訪問鏈表中的下一個或者前一個（需要儲存反向的指針，見下面的雙向鏈表）節點。
相對于下面的雙向鏈表，這種普通的，每個節點只有一個指針的鏈表也叫單向鏈表，或者單鏈表，通常用在每次都只會按順序遍歷這個鏈表的時候（例如圖的鄰接表，通常都是按固定順序訪問的）。
鏈表也有很多種不同的變化：
一種更復雜的鏈表是“雙向鏈表”或“雙面鏈表”。每個節點有兩個連接：一個指向前一個節點，（當此“連接”為第一個“連接”時，指向空值或者空列表）；而另一個指向下一個節點，（當此“連接”為最后一個“連接”時，指向空值或者空列表）
Image:Doubly-linked-list.svg''一個雙向鏈表有三個整數值: 數值, 向后的節點鏈接, 向前的節點鏈接''
在一些低級語言中, XOR-linking 提供一種在雙向鏈表中通過用一個詞來表示兩個鏈接（前后），我們通常不提倡這種做法。
雙向鏈表也叫雙鏈表。雙向鏈表中不僅有指向后一個節點的指針，還有指向前一個節點的指針。這樣可以從任何一個節點訪問前一個節點，當然也可以訪問后一個節點，以至整個鏈表。一般是在需要大批量的另外儲存數據在鏈表中的位置的時候用。雙向鏈表也可以配合下面的其他鏈表的擴展使用。
由于另外儲存了指向鏈表內容的指針，并且可能會修改相鄰的節點，有的時候第一個節點可能會被刪除或者在之前添加一個新的節點。這時候就要修改指向首個節點的指針。有一種方便的可以消除這種特殊情況的方法是在最后一個節點之后、第一個節點之前儲存一個永遠不會被刪除或者移動的虛擬節點，形成一個下面說的循環鏈表。這個虛擬節點之后的節點就是真正的第一個節點。這種情況通常可以用這個虛擬節點直接表示這個鏈表，對于把鏈表單獨的存在數組里的情況，也可以直接用這個數組表示鏈表并用第0個或者第-1個（如果編譯器支持）節點固定的表示這個虛擬節點。
在一個 循環鏈表中, 首節點和末節點被連接在一起。這種方式在單向和雙向鏈表中皆可實現。要轉換一個循環鏈表，你開始于任意一個節點然后沿著列表的任一方向直到返回開始的節點。再來看另一種方法，循環鏈表可以被視為“無頭無尾”。這種列表很利于節約數據存儲緩存， 假定你在一個列表中有一個對象并且希望所有其他對象迭代在一個非特殊的排列下。
指向整個列表的指針可以被稱作存取指針。
Image:Circularly-linked-list.svg''用單向鏈表構建的循環鏈表''
循環鏈表中第一個節點之前就是最后一個節點，反之亦然。循環鏈表的無邊界使得在這樣的鏈表上設計算法會比普通鏈表更加容易。對于新加入的節點應該是在第一個節點之前還是最后一個節點之后可以根據實際要求靈活處理，區別不大(詳見下面實例代碼)。當然，如果只會在最后插入數據（或者只會在之前），處理也是很容易的。
另外有一種模擬的循環鏈表，就是在訪問到最后一個節點之后的時候，手工的跳轉到第一個節點。訪問到第一個節點之前的時候也一樣。這樣也可以實現循環鏈表的功能，在直接用循環鏈表比較麻煩或者可能會出現問題的時候可以用。
塊狀鏈表本身是一個鏈表，但是鏈表儲存的并不是一般的數據，而是由這些數據組成的順序表。每一個塊狀鏈表的節點，也就是順序表，可以被叫做一個塊。
塊狀鏈表通過使用可變的順序表的長度和特殊的插入、刪除方式，可以在達到的復雜度。塊狀鏈表另一個特點是相對于普通鏈表來說節省內存，因為不用保存指向每一個數據節點的指針。
根據情況，也可以自己設計鏈表的其它擴展。但是一般不會在邊上附加數據，因為鏈表的點和邊基本上是一一對應的（除了第一個或者最后一個節點，但是也不會產生特殊情況）。不過有一個特例是如果鏈表支持在鏈表的一段中把前和后指針反向，反向標記加在邊上可能會更方便。
對于非線性的鏈表，可以參見相關的其他數據結構，例如樹、圖。另外有一種基于多個線性鏈表的數據結構：跳表，插入、刪除和查找等基本操作的速度可以達到O(nlogn)，和平衡樹一樣。
鏈表中的節點不需要以特定的方式存儲，但是集中存儲也是可以的，主要分下面這幾種具體的存儲方法：
;共用存儲空間:鏈表的節點和其它的數據共用存儲空間，優點是可以存儲無限多的內容（不過要處理器支持這個大小，并且存儲空間足夠的情況下），不需要提前分配內存；缺點是由于內容分散，有時候可能不方便調試。
;獨立存儲空間:一個鏈表或者多個鏈表使用獨立的存儲空間，一般用數組或者類似結構實現，優點是可以自動獲得一個附加數據：唯一的編號，并且方便調試；缺點是不能動態的分配內存。當然，另外的在上面加一層塊狀鏈表用來分配內存也是可以的，這樣就解決了這個問題。這種方法有時候被叫做數組模擬鏈表，但是事實上只是用表示在數組中的位置的下標索引代替了指向內存地址的指針，這種下標索引其實也是邏輯上的指針，整個結構還是鏈表，并不算是被模擬的（但是可以說成是用數組實現的鏈表）。
鏈表用來構建許多其它數據結構，如堆棧，隊列和他們的衍生。
節點的數據域也可以成為另一個鏈表。通過這種手段，我們可以用列表來構建許多鏈性數據結構；這個實例產生于Lisp編程語言，在Lisp中鏈表是初級數據結構，并且現在成為了常見的基礎編程模式。
有時候，鏈表用來生成聯合數組，在這種情況下我們稱之為聯合數列。這種情況下用鏈表會優于其它數據結構，如自平對分查找樹（self-balancing binary search trees）甚至是一些小的數據集合。不管怎樣，一些時候一個鏈表在這樣一個樹中建立一個節點子集，并且以此來更有效率地轉換這個集合。
范例代碼是一個ADT（抽象數據類型）雙向環形鏈表的基本操作部分的實例（未包含線程安全機制），全部遵從ANSI C標準。
#ifndef LLIST_H
#define LLIST_H
typedef void node_proc_fun_t(void*);
typedef int node_comp_fun_t(const void*, const void*);
typedef void LLIST_T;
LLIST_T *llist_new(int elmsize);
int llist_delete(LLIST_T *ptr);
int llist_node_append(LLIST_T *ptr, const void *datap);
int llist_node_prepend(LLIST_T *ptr, const void *datap);
int llist_travel(LLIST_T *ptr, node_proc_fun_t *proc);
void llist_node_delete(LLIST_T *ptr, node_comp_fun_t *comp, const void *key);
void *llist_node_find(LLIST_T *ptr, node_comp_fun_t *comp, const void *key);
struct node_st {
void *datap;
struct node_st *next, *prev;
struct llit_st {
struct node_st head;
int lmsize;
int elmnr;
LLIST_T *
llist_new(int elmsize) {
struct llist_st *newlist;
newlist = malloc(sizeof(struct llist_st));
if (newlist == NULL)
newlist->head.datap = NULL;
newlist->head.next = &newlist->head;
newlist->head.prev = &newlist->head;
newlist->elmsize = elmsize;
return (void *)newlist;
int llist_delete(LLIST_T *ptr) {
struct llist_st *me = ptr;
struct node_st *curr, *save;
for (curr = me->head.next ;
curr != &me->head ; curr = save) {
save = curr->next;
free(curr->datap);
free(curr);
free(me);
int llist_node_append(LLIST_T *ptr, const void *datap) {
struct node_st *newnodep;
newnodep = malloc(sizeof(struct node_st));
if (newnodep == NULL)
newnodep->datap = malloc(me->elmsize);
if (newnodep->datap == NULL) {
free(newnodep);
memcpy(newnodep->datap, datap, me->elmsize);
me->head.prev->next = newnodep;
newnodep->prev = me->head.prev;
me->head.prev = newnodep;
newnodep->next = &me->head;
int llist_node_prepend(LLIST_T *ptr, const void *datap) {
me->head.next->prev = newnodep;
newnodep->next = me->head.next;
me->head.next = newnodep;
newnodep->prev = &me->head;
int llist_travel(LLIST_T *ptr, node_proc_fun_t *proc) {
struct node_st *curr;
for (curr = me->head.next;
curr != &me->head ; curr = curr->next)
proc(curr->datap); // proc(something you like)
void llist_node_delete(LLIST_T *ptr,
node_comp_fun_t *comp,
const void *key) {
curr != &me->head; curr = curr->next) {
if ( (*comp)(curr->datap, key) == 0 ) {
struct node_st *_next, *_prev;
_prev = curr->prev; _next = curr->next;
_prev->next = _next; _next->prev = _prev;
void *llist_node_find(LLIST_T *ptr,
node_comp_fun_t *comp, const void *key) {
if ( (*comp)(curr->datap, key) == 0 )
return curr->datap;
以下代碼摘自Linux內核2.6.21.5源碼(部分)，展示了鏈表的另一種實現思路，未采用ANSI C標準，采用GNU C標準，遵從GPL版權許可。
struct list_head {
struct list_head *next, *prev;
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD(struct list_head *list) {
list->next = list;
list->prev = list;
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next) {
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
static inline void list_add(struct list_head *new, struct list_head *head) {
__list_add(new, head, head->next);
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);
entry->next = NULL;
entry->prev = NULL;
#define __list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
#define list_for_each_entry(pos, head, member)                          \
for (pos = list_entry((head)->next, typeof(*pos), member);      \
prefetch(pos->member.next), &pos->member != (head);        \
pos = list_entry(pos->member.next, typeof(*pos), member))
常用于組織檢索較少，而刪除、添加、遍歷較多的數據。
如果與上述情形相反，應采用其他數據結構或者與其他數據結構組合使用。
*  Description from the Dictionary of Algorithms and Data Structures
*  Introduction to Linked Lists, Stanford University Computer Science Library
*  Linked List Problems, Stanford University Computer Science Library
*  Open Data Structures - Chapter 3 - Linked Lists, Pat Morin
*  Patent for the idea of having nodes which are in several linked lists simultaneously (note that this technique was widely used for many decades before the patent was granted)
佇列，又稱為-{zh-hans:佇列;zh-hant:隊列}-（queue），計算機科學中的一種抽象資料型別，是先進先出（FIFO, First-In-First-Out）的線性表。在具體應用中通常用鏈表或者數組來實現。隊列只允許在后端（稱為''rear''）進行插入操作，在前端（稱為''front''）進行刪除操作。
隊列的操作方式和堆棧類似，唯一的區別在于隊列只允許新數據在后端進行添加。
單鏈隊列使用鏈表作為基本數據結構，所以不存在偽溢出的問題，隊列長度也沒有限制。但插入和讀取的時間代價較高
/* c3-2.h 單鏈隊列－－隊列的鏈式存儲結構 */
typedef struct QNode
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
QueuePtr front,rear; /* 隊頭、隊尾指針 */
}LinkQueue;
/* bo3-2.c 鏈隊列(存儲結構由c3-2.h定義)的基本操作(9個) */
void InitQueue(LinkQueue *Q)
{	/* 構造一個空隊列Q */
(*Q).front=(*Q).rear=(QueuePtr)malloc(sizeof(QNode));
if(!(*Q).front)
exit(OVERFLOW);
(*Q).front->next=NULL;
void DestroyQueue(LinkQueue *Q)
{	/* 銷毀隊列Q(無論空否均可) */
while((*Q).front)
(*Q).rear=(*Q).front->next;
free((*Q).front);
(*Q).front=(*Q).rear;
void ClearQueue(LinkQueue *Q)
{	/* 將Q清為空隊列 */
QueuePtr p,q;
(*Q).rear=(*Q).front;
p=(*Q).front->next;
while(p)
q=p;
p=p->next;
free(q);
Status QueueEmpty(LinkQueue Q)
{	/* 若Q為空隊列，則返回TRUE，否則返回FALSE */
if(Q.front->next==NULL)
int QueueLength(LinkQueue Q)
{	/* 求隊列的長度 */
int i=0;
QueuePtr p;
p=Q.front;
while(Q.rear!=p)
return i;
Status GetHead_Q(LinkQueue Q,QElemType *e) /* 避免與bo2-6.c重名 */
{	/* 若隊列不空，則用e返回Q的隊頭元素，并返回OK，否則返回ERROR */
if(Q.front==Q.rear)
p=Q.front->next;
*e=p->data;
void EnQueue(LinkQueue *Q,QElemType e)
{	/* 插入元素e為Q的新的隊尾元素 */
QueuePtr p=(QueuePtr)malloc(sizeof(QNode));
if(!p) /* 存儲分配失敗 */
p->data=e;
p->next=NULL;
(*Q).rear->next=p;
(*Q).rear=p;
Status DeQueue(LinkQueue *Q,QElemType *e)
{	/* 若隊列不空，刪除Q的隊頭元素，用e返回其值，并返回OK，否則返回ERROR */
if((*Q).front==(*Q).rear)
(*Q).front->next=p->next;
if((*Q).rear==p)
free(p);
void QueueTraverse(LinkQueue Q,void(*vi)(QElemType))
{	/* 從隊頭到隊尾依次對隊列Q中每個元素調用函數vi */
vi(p->data);
printf("\n");
循環隊列可以更簡單防止偽溢出的發生，但隊列大小是固定的。
// 隊列的順序存儲結構(循環隊列)
#define MAX_QSIZE 5 // 最大隊列長度+1
typedef struct {
int *base; // 初始化的動態分配存儲空間
int front; // 頭指針，若隊列不空，指向隊列頭元素
int rear; // 尾指針，若隊列不空，指向隊列尾元素的下一個位置
} SqQueue;
// 構造一個空隊列Q
SqQueue* Q_Init {
SqQueue *Q = (SqQueue*)malloc(sizeof(SqQueue));
// 存儲分配失敗
if (!Q){
Q->base = (int *)malloc(MAX_QSIZE * sizeof(int));
if (!Q->base){
Q->front = Q->rear = 0;
return Q;
// 銷毀隊列Q，Q不再存在
void Q_Destroy(SqQueue *Q) {
if (Q->base)
free(Q->base);
Q->base = NULL;
free(Q);
// 將Q清為空隊列
void Q_Clear(SqQueue *Q) {
// 若隊列Q為空隊列，則返回1；否則返回-1
int Q_Empty(SqQueue Q) {
if (Q.front == Q.rear) // 隊列空的標志
// 返回Q的元素個數，即隊列的長度
int Q_Length(SqQueue Q) {
return (Q.rear - Q.front + MAX_QSIZE) % MAX_QSIZE;
// 若隊列不空，則用e返回Q的隊頭元素，并返回OK；否則返回ERROR
int Q_GetHead(SqQueue Q, int &e) {
if (Q.front == Q.rear) // 隊列空
e = Q.baseQ.front;
// 打印隊列中的內容
void Q_Print(SqQueue Q) {
int p = Q.front;
while (Q.rear != p) {
cout rear + 1) % MAX_QSIZE == Q->front) // 隊列滿
Q->baseQ->rear = e;
Q->rear = (Q->rear + 1) % MAX_QSIZE;
// 若隊列不空，則刪除Q的隊頭元素，用e返回其值，并返回1；否則返回-1
int Q_Poll(SqQueue *Q, int &e) {
if (Q->front == Q->rear) // 隊列空
e = Q->baseQ->front;
Q->front = (Q->front + 1) % MAX_QSIZE;
#define MAX_QSIZE 10 // 最大隊列長度+1
// 陣列隊列的存儲結構
struct Queue {
int ArrayMAX_QSIZE; // 陣列空間大小
int front; // 隊頭
int rear; // 隊尾
int length; // 隊列長度
Queue* Q_Init {
Queue *Q = (Queue*)malloc(sizeof(Queue));
Q->front = Q->rear = Q->length = 0;
void Q_Clear(Queue *Q) {
//清除頭尾下標和長度
int Q_Put(Queue *Q, int x) {
//如果當前元素數量等于最大數量 返回 -1
if (Q->rear + 1 == MAX_QSIZE) {
Q->ArrayQ->rear = x;
Q->rear = Q->rear + 1;
//length + 1
Q->length = Q->length + 1;
int Q_Poll(Queue *Q) {
if (Q->front + 1 == MAX_QSIZE)
int x = Q->ArrayQ->front;
Q->front = Q->front + 1;
// 移出后減少1
Q->length = Q->length - 1;
return x;
一棵有9個節點且深度為3的二元樹，其根節點的值為2，它既不平衡亦未經過排序
一棵簡單的滿二叉樹
在電腦科學中，二元樹是每個節點最多只有兩個分支（即不存在分支度大於2的節點）的樹結構。通常分支被稱作“左子樹”或“右子樹”。二元樹的分支具有左右次序，不能隨意顛倒。
二元樹的第層至多擁有個節點；深度為的二元樹至多總共有個節點（定義根節點所在深度 ），而總計擁有節點數符合的，稱為「滿二元樹」；深度為有個節點的二元樹，當且僅當其中的每一節點，都可以和深度的滿二元樹，序號1到的節點一對一對應時，稱為完全二元樹。對任何一棵非空的二元樹，如果其葉片（終端節點）數為，分支度為2的節點數為，則。
與普通樹不同，普通樹的節點個數至少為1，而二元樹的節點個數可以為0；普通樹節點的最大分支度沒有限制，而二元樹節點的最大分支度為2；普通樹的節點無左、右次序之分，而二元樹的節點有左、右次序之分。
二元樹通常作為資料結構應用，典型用法是對節點定義一個標記函數，將一些值與每個節點相關聯。這樣標記的二元樹就可以實現二元搜尋樹和二元堆積，並應用於高效率的搜索和排序。
二元樹是一個連通的無環圖，並且每一個頂點的度不大於3。有根二元樹還要滿足根節點的度不大於2。有了根節點之後，每個頂點定義了唯一的父節點，和最多2個子節點。然而，沒有足夠的資訊來區分左節點和右節點。如果不考慮連通性，允許圖中有多個連通分量，這樣的結構叫做森林。
二元樹是一個有根樹，並且每個節點最多有2個子節點。非空的二叉樹，若樹葉總數為 n0，分支度為2的總數為 n2，則 n0 = n2 + 1。
i，（假設根節點的索引為0）則在它左子節點的索引會是，以及右子節點會是；而它的父節點（如果有）索引則為'。這種方法更有利於緊湊存儲和更好的訪問的局部性，特別是在前序遍歷中。然而，它需要連續的存儲空間，這樣在存儲高度為''h''的''n''個節點所組成的一般樹時，將浪費很多空間。在最糟糕的情況下，如果深度為h的二元樹其每個節點都只有右孩子，則該存儲結構需要佔用的空間，實際上卻有h個節點，浪費了不少空間，是順序存儲結構的一大缺點。
/* 二叉樹的順序存儲表示 */
#define MAX_TREE_SIZE 100 /* 二叉樹的最大節點數 */
typedef TElemType SqBiTreeMAX_TREE_SIZE; /* 0號單元存儲根節點 */
int level,order; /* 即節點的層(按滿二叉樹計算) */
}position;
/* 二元樹的順序存儲的基本操作(23個)*/
#define ClearBiTree InitBiTree /* 在順序存儲結構中，兩函數完全一樣 */
#define DestroyBiTree InitBiTree /* 在順序存儲結構中，兩函數完全一樣 */
void InitBiTree(SqBiTree T) －－－（SqBiTree & T）
{ /* 構造空二元樹T。因為T是陣列名稱，故不需要& */
for(i=0;i
void CreateBiTree(SqBiTree T)
{ /* 按層序次序輸入二叉樹中結點的值(字元型或整型), 構造順序存儲的二叉樹T */
#if CHAR /* 結點類型為字元 */
int l;
char sMAX_TREE_SIZE;
InitBiTree(T); /* 構造空二元樹T */
printf("請按層序輸入結點的值(字元)，空格表示空結點，節點數≦%d:\n",MAX_TREE_SIZE);
gets(s); /* 輸入字串 */
l=strlen(s); /* 求字串的長度 */
for(;i
int BiTreeDepth(SqBiTree T)
{ /* 初始條件：二元樹T存在。操作結果：返回T的深度 */
int i,j=-1;
for(i=MAX_TREE_SIZE-1;i>=0;i--) /* 找到最後一個節點 */
if(Ti!=Nil)
i++; /* 為了便於計算 */
while(i>=pow(2,j));   /*pow是原型為double pow( double x, double y ),計算x的y次方,h = log2k + 1來計算二元樹的深度*/
return j;
Status Root(SqBiTree T,TElemType *e)
{ /* 初始條件：二元樹T存在。操作結果：當T不空，用e返回T的根，返回OK；否則返回ERROR，e無定義 */
if(BiTreeEmpty(T)) /* T空 */
TElemType Value(SqBiTree T,position e)
{ /* 初始條件：二元樹T存在，e是T中某個結點(的位置) */
/* 操作結果：返回處於位置e(層,本層序號)的結點的值 */
return T(int)pow(2,e.level-1)+e.order-2;
Status Assign(SqBiTree T,position e,TElemType value)
{ /* 初始條件：二叉樹T存在，e是T中某個結點(的位置) */
/* 操作結果：給處於位置e(層,本層序號)的結點賦新值value */
int i=(int)pow(2,e.level-1)+e.order-2; /* 將層、本層序號轉為矩陣的序號 */
if(value!=Nil&&T(i+1)/2-1==Nil) /* 給葉子賦非空值但雙親為空 */
else if(value==Nil&&(Ti*2+1!=Nil
Ti*2+2!=Nil)) /*  給雙親賦空值但有葉子(不空) */
Ti=value;
TElemType Parent(SqBiTree T,TElemType e)
{ /* 初始條件：二叉樹T存在，e是T中某個結點 */
/* 操作結果：若e是T的非根結點，則返回它的雙親，否則返回＂空＂ */
if(T0==Nil) /* 空樹 */
return Nil;
for(i=1;i
TElemType LeftChild(SqBiTree T,TElemType e)
{ /* 初始條件：二叉樹T存在，e是T中某個結點。操作結果：返回e的左孩子。若e無左孩子,則返回"空" */
TElemType RightChild(SqBiTree T,TElemType e)
{ /* 初始條件：二叉樹T存在，e是T中某個結點。操作結果：返回e的右孩子。若e無右孩子,則返回"空" */
TElemType LeftSibling(SqBiTree T,TElemType e)
/* 操作結果：返回e的左兄弟。若e是T的左孩子或無左兄弟，則返回＂空＂ */
TElemType RightSibling(SqBiTree T,TElemType e)
/* 操作結果：返回e的右兄弟。若e是T的右孩子或無右兄弟，則返回＂空＂ */
void Move(SqBiTree q,int j,SqBiTree T,int i) /* InsertChild用到。加 */
{ /* 把從q的j結點開始的子樹移為從T的i結點開始的子樹 */
if(q2*j+1!=Nil) /* q的左子樹不空 */
Move(q,(2*j+1),T,(2*i+1)); /* 把q的j結點的左子樹移為T的i結點的左子樹 */
if(q2*j+2!=Nil) /* q的右子樹不空 */
Move(q,(2*j+2),T,(2*i+2)); /* 把q的j結點的右子樹移為T的i結點的右子樹 */
Ti=qj; /* 把q的j結點移為T的i結點 */
qj=Nil; /* 把q的j結點置空 */
void InsertChild(SqBiTree T,TElemType p,int LR,SqBiTree c)
{ /* 初始條件：二叉樹T存在，p是T中某個結點的值，LR為0或1，非空二叉樹c與T不相交且右子樹為空 */
/* 操作結果: 根據LR為0或1,插入c為T中p結點的左或右子樹。p結點的原有左或右子樹則成為c的右子樹 */
int j,k,i=0;
for(j=0;j
typedef int QElemType; /* 設佇列元素類型為整型(序號) */
#include "c3-2.h" /* 鏈佇列 */
#include "bo3-2.c" /* 鏈佇列的基本操作 */
Status DeleteChild(SqBiTree T,position p,int LR)
{ /* 初始條件：二叉樹T存在，p指向T中某個結點，LR為1或0 */
/* 操作結果：根據LR為1或0，刪除T中p所指結點的左或右子樹 */
Status k=OK; /* 佇列不空的標誌 */
LinkQueue q;
InitQueue(&q); /* 初始化佇列，用於存放待刪除的結點 */
i=(int)pow(2,p.level-1)+p.order-2; /* 將層、本層序號轉為矩陣的序號 */
if(Ti==Nil) /* 此結點空 */
i=i*2+1+LR; /* 待刪除子樹的根結點在矩陣中的序號 */
while(k)
if(T2*i+1!=Nil) /* 左結點不空 */
EnQueue(&q,2*i+1); /* 入隊左結點的序號 */
if(T2*i+2!=Nil) /* 右結點不空 */
EnQueue(&q,2*i+2); /* 入隊右結點的序號 */
Ti=Nil; /* 刪除此結點 */
k=DeQueue(&q,&i); /* 佇列不空 */
void(*VisitFunc)(TElemType); /* 函數變數 */
void PreTraverse(SqBiTree T,int e)
{ /* PreOrderTraverse調用 */
VisitFunc(Te);
if(T2*e+1!=Nil) /* 左子樹不空 */
PreTraverse(T,2*e+1);
if(T2*e+2!=Nil) /* 右子樹不空 */
PreTraverse(T,2*e+2);
void PreOrderTraverse(SqBiTree T,void(*Visit)(TElemType))
{ /* 初始條件：二叉樹存在，Visit是對結點操作的應用函數 */
/* 操作結果：先序遍歷T，對每個結點調用函數Visit一次且僅一次 */
VisitFunc=Visit;
if(!BiTreeEmpty(T)) /* 樹不空 */
PreTraverse(T,0);
void InTraverse(SqBiTree T,int e)
{ /* InOrderTraverse調用 */
InTraverse(T,2*e+1);
InTraverse(T,2*e+2);
void InOrderTraverse(SqBiTree T,void(*Visit)(TElemType))
/* 操作結果：中序遍歷T，對每個結點調用函數Visit一次且僅一次 */
InTraverse(T,0);
void PostTraverse(SqBiTree T,int e)
{ /* PostOrderTraverse調用 */
PostTraverse(T,2*e+1);
PostTraverse(T,2*e+2);
void PostOrderTraverse(SqBiTree T,void(*Visit)(TElemType))
{ /* 初始條件：二叉樹T存在，Visit是對結點操作的應用函數 */
/* 操作結果：後序遍歷T，對每個結點調用函數Visit一次且僅一次 */
PostTraverse(T,0);
void LevelOrderTraverse(SqBiTree T,void(*Visit)(TElemType))
{ /* 層序遍歷二叉樹 */
int i=MAX_TREE_SIZE-1,j;
while(Ti==Nil)
i--; /* 找到最後一個非空結點的序號 */
void Print(SqBiTree T)
{ /* 逐層、按本層序號輸出二叉樹 */
int j,k;
position p;
TElemType e;
for(j=1;j
402x402px
在使用記錄或記憶體位址指標的程式設計語言中，二叉樹通常用樹結點結構來存儲。有時也包含指向唯一的父節點的指標。如果一個結點的子結點個數小於2，一些子結點指針可能為空值，或者為特殊的哨兵結點。
使用鏈表能避免順序儲存浪費空間的問題，演算法和結構相對簡單，但使用二叉鏈表，由於缺乏父鏈的指引，在找回父節點時需要重新掃描樹得知父節點的節點位址。
/* 二叉樹的二叉鏈表存儲表示 */
typedef struct BiTNode
TElemType data;
struct BiTNode *lchild,*rchild; /* 左右孩子指針 */
}BiTNode,*BiTree;
/* 二叉樹的二叉鏈表存儲的基本操作(22個) */
#define ClearBiTree DestroyBiTree /* 清空二叉樹和銷毀二叉樹的操作一樣 */
#include"func6-3.c"
/* 包括InitBiTree、DestroyBiTree、PreOrderTraverse和InOrderTraverse4函數 */
void CreateBiTree(BiTree *T)
{ /* 演算法6.4：按先序次序輸入二叉樹中結點的值(可為字元型或整型，在主程中定義)，*/
/* 構造二叉鏈表表示的二叉樹T。變數Nil表示空(子)樹。有改動 */
TElemType ch;
scanf(form,&ch);
if(ch==Nil) /* 空 */
*T=(BiTree)malloc(sizeof(BiTNode)); /* 生成根結點 */
if(!*T)
(*T)->data=ch;
CreateBiTree(&(*T)->lchild); /* 構造左子樹 */
CreateBiTree(&(*T)->rchild); /* 構造右子樹 */
Status BiTreeEmpty(BiTree T)
{ /* 初始條件：二叉樹T存在。操作結果：若T為空二叉樹，則返回TRUE，否則FALSE */
int BiTreeDepth(BiTree T)
{ /* 初始條件：二叉樹T存在。操作結果：返回T的深度 */
if(T==NULL)  /*如果T=NULL,這樣寫便于理解，當然也可以寫成if（!T）*/;
return 0; /* 空樹深度為0 */
if(T->lchild)
i=BiTreeDepth(T->lchild); /* i為左子樹的深度 */
if(T->rchild)
j=BiTreeDepth(T->rchild); /* j為右子樹的深度 */
return i>j?i+1:j+1; /* T的深度為其左右子樹的深度中的大者+1 */
TElemType Root(BiTree T)
{ /* 初始條件：二叉樹T存在。操作結果：返回T的根 */
if(BiTreeEmpty(T))
return T->data;
TElemType Value(BiTree p)
{ /* 初始條件：二叉樹T存在，p指向T中某個結點。操作結果：返回p所指結點的值 */
return p->data;
void Assign(BiTree p,TElemType value)
{ /* 給p所指結點賦值為value */
p->data=value;
typedef BiTree QElemType; /* 設佇列元素為二叉樹的指針類型 */
#include"c3-2.h" /* 鏈佇列 */
#include"bo3-2.c" /* 鏈佇列的基本操作 */
TElemType Parent(BiTree T,TElemType e)
/* 操作結果：若e是T的非根結點，則返回它的雙親，否則返回＂空＂*/
QElemType a;
if(T) /* 非空樹 */
InitQueue(&q); /* 初始化佇列 */
EnQueue(&q,T); /* 樹根指針入隊 */
while(!QueueEmpty(q)) /* 隊不空 */
DeQueue(&q,&a); /* 出隊，佇列元素賦給a */
if(a->lchild&&a->lchild->data==e
a->rchild&&a->rchild->data==e)
/* 找到e(是其左或右孩子) */
return a->data; /* 返回e的雙親的值 */
else /* 沒找到e，則入隊其左右孩子指針(如果非空) */
if(a->lchild)
EnQueue(&q,a->lchild);
if(a->rchild)
EnQueue(&q,a->rchild);
return Nil; /* 樹空或沒找到e */
BiTree Point(BiTree T,TElemType s)
{ /* 返回二叉樹T中指向元素值為s的結點的指標。另加 */
EnQueue(&q,T); /* 根指針入隊 */
if(a->data==s)
if(a->lchild) /* 有左孩子 */
EnQueue(&q,a->lchild); /* 入隊左孩子 */
if(a->rchild) /* 有右孩子 */
EnQueue(&q,a->rchild); /* 入隊右孩子 */
TElemType LeftChild(BiTree T,TElemType e)
BiTree a;
a=Point(T,e); /* a是結點e的指針 */
if(a&&a->lchild) /* T中存在結點e且e存在左孩子 */
return a->lchild->data; /* 返回e的左孩子的值 */
return Nil; /* 其餘情況返回空 */
TElemType RightChild(BiTree T,TElemType e)
if(a&&a->rchild) /* T中存在結點e且e存在右孩子 */
return a->rchild->data; /* 返回e的右孩子的值 */
TElemType LeftSibling(BiTree T,TElemType e)
/* 操作結果：返回e的左兄弟。若e是T的左孩子或無左兄弟，則返回＂空＂*/
TElemType a;
BiTree p;
a=Parent(T,e); /* a為e的雙親 */
if(a!=Nil) /* 找到e的雙親 */
p=Point(T,a); /* p為指向結點a的指標 */
if(p->lchild&&p->rchild&&p->rchild->data==e) /* p存在左右孩子且右孩子是e */
return p->lchild->data; /* 返回p的左孩子(e的左兄弟) */
TElemType RightSibling(BiTree T,TElemType e)
/* 操作結果：返回e的右兄弟。若e是T的右孩子或無右兄弟，則返回＂空＂*/
if(p->lchild&&p->rchild&&p->lchild->data==e) /* p存在左右孩子且左孩子是e */
return p->rchild->data; /* 返回p的右孩子(e的右兄弟) */
Status InsertChild(BiTree p,int LR,BiTree c) /* 形參T無用 */
{ /* 初始條件：二叉樹T存在，p指向T中某個結點，LR為0或1，非空二叉樹c與T不相交且右子樹為空 */
/* 操作結果：根據LR為0或1，插入c為T中p所指結點的左或右子樹。p所指結點的 */
/*           原有左或右子樹則成為c的右子樹 */
if(p) /* p不空 */
if(LR==0)
c->rchild=p->lchild;
p->lchild=c;
else /* LR==1 */
c->rchild=p->rchild;
p->rchild=c;
return ERROR; /* p空 */
Status DeleteChild(BiTree p,int LR) /* 形參T無用 */
{ /* 初始條件：二叉樹T存在，p指向T中某個結點，LR為0或1 */
/* 操作結果：根據LR為0或1，刪除T中p所指結點的左或右子樹 */
if(LR==0) /* 刪除左子樹 */
ClearBiTree(&p->lchild);
else /* 刪除右子樹 */
ClearBiTree(&p->rchild);
typedef BiTree SElemType; /* 設棧元素為二叉樹的指針類型 */
#include"c3-1.h" /* 順序棧 */
#include"bo3-1.c" /* 順序棧的基本操作 */
void InOrderTraverse1(BiTree T,void(*Visit)(TElemType))
{ /* 採用二叉鏈表存儲結構，Visit是對資料元素操作的應用函數。演算法6.3，有改動 */
/* 中序遍歷二叉樹T的非遞迴演算法(利用棧)，對每個資料元素調用函數Visit */
SqStack S;
InitStack(&S);
while(T
StackEmpty(S))
{ /* 根指針進棧，遍歷左子樹 */
Push(&S,T);
T=T->lchild;
{ /* 根指針退棧，訪問根結點，遍歷右子樹 */
Pop(&S,&T);
Visit(T->data);
T=T->rchild;
void InOrderTraverse2(BiTree T,void(*Visit)(TElemType))
{ /* 採用二叉鏈表存儲結構，Visit是對資料元素操作的應用函數。演算法6.2，有改動 */
Push(&S,T); /* 根指針進棧 */
while(!StackEmpty(S))
while(GetTop(S,&p)&&p)
Push(&S,p->lchild); /* 向左走到盡頭 */
Pop(&S,&p); /* 空指針退棧 */
if(!StackEmpty(S))
{ /* 訪問結點，向右一步 */
Pop(&S,&p);
Visit(p->data);
Push(&S,p->rchild);
void PostOrderTraverse(BiTree T,void(*Visit)(TElemType))
/* 操作結果：後序遞迴遍歷T，對每個結點調用函數Visit一次且僅一次 */
if(T) /* T不空 */
PostOrderTraverse(T->lchild,Visit); /* 先後序遍歷左子樹 */
PostOrderTraverse(T->rchild,Visit); /* 再後序遍歷右子樹 */
Visit(T->data); /* 最後訪問根結點 */
void LevelOrderTraverse(BiTree T,void(*Visit)(TElemType))
/* 操作結果：層序遞迴遍歷T(利用佇列)，對每個結點調用函數Visit一次且僅一次 */
InitQueue(&q); /* 初始化佇列q */
while(!QueueEmpty(q)) /* 佇列不空 */
DeQueue(&q,&a); /* 出隊元素(指標),賦給a */
Visit(a->data); /* 訪問a所指結點 */
if(a->lchild!=NULL) /* a有左孩子 */
EnQueue(&q,a->lchild); /* 入隊a的左孩子 */
if(a->rchild!=NULL) /* a有右孩子 */
EnQueue(&q,a->rchild); /* 入隊a的右孩子 */
基於三叉鏈表的二叉樹的邏輯結構
改進於二叉鏈表，增加父節點的指引，能更好地實現節點間的訪問，不過演算法相對複雜。
當二叉樹用三叉鏈表表示時，有N個結點，就會有N+2個空指針。
/* 二叉樹的三叉鏈表存儲表示 */
typedef struct BiTPNode
struct BiTPNode *parent,*lchild,*rchild; /* 父、左右孩子指針 */
}BiTPNode,*BiPTree;
/* 二叉樹的三叉鏈表存儲的基本操作(21個) */
void InitBiTree(BiPTree *T)
{ /* 操作結果：構造空二叉樹T */
void DestroyBiTree(BiPTree *T)
{ /* 初始條件：二叉樹T存在。操作結果：銷毀二叉樹T */
if(*T) /* 非空樹 */
if((*T)->lchild) /* 有左孩子 */
DestroyBiTree(&(*T)->lchild); /* 銷毀左孩子子樹 */
if((*T)->rchild) /* 有右孩子 */
DestroyBiTree(&(*T)->rchild); /* 銷毀右孩子子樹 */
free(*T); /* 釋放根結點 */
*T=NULL; /* 空指針賦0 */
void CreateBiTree(BiPTree *T)
{ /* 按先序次序輸入二叉樹中結點的值(可為字元型或整型，在主程中定義)，*/
/* 構造三叉鏈表表示的二叉樹T */
*T=(BiPTree)malloc(sizeof(BiTPNode)); /* 動態生成根結點 */
(*T)->data=ch; /* 給根結點賦值 */
(*T)->parent=NULL; /* 根結點無雙親 */
(*T)->lchild->parent=*T; /* 給左孩子的雙親域賦值 */
(*T)->rchild->parent=*T; /* 給右孩子的雙親域賦值 */
Status BiTreeEmpty(BiPTree T)
int BiTreeDepth(BiPTree T)
if(!T)
TElemType Root(BiPTree T)
TElemType Value(BiPTree p)
void Assign(BiPTree p,TElemType value)
typedef BiPTree QElemType; /* 設佇列元素為二叉樹的指針類型 */
BiPTree Point(BiPTree T,TElemType e)
{ /* 返回二叉樹T中指向元素值為e的結點的指標。加 */
EnQueue(&q,T); /* 根結點入隊 */
if(a->data==e)
TElemType Parent(BiPTree T,TElemType e)
BiPTree a;
if(a&&a!=T) /* T中存在結點e且e是非根結點 */
return a->parent->data; /* 返回e的雙親的值 */
TElemType LeftChild(BiPTree T,TElemType e)
TElemType RightChild(BiPTree T,TElemType e)
TElemType LeftSibling(BiPTree T,TElemType e)
if(a&&a!=T&&a->parent->lchild&&a->parent->lchild!=a) /* T中存在結點e且e存在左兄弟 */
return a->parent->lchild->data; /* 返回e的左兄弟的值 */
TElemType RightSibling(BiPTree T,TElemType e)
if(a&&a!=T&&a->parent->rchild&&a->parent->rchild!=a) /* T中存在結點e且e存在右兄弟 */
return a->parent->rchild->data; /* 返回e的右兄弟的值 */
Status InsertChild(BiPTree p,int LR,BiPTree c) /* 形參T無用 */
/* 操作結果：根據LR為0或1，插入c為T中p所指結點的左或右子樹。p所指結點 */
/*           的原有左或右子樹則成為c的右子樹 */
if(c->rchild) /* c有右孩子(p原有左孩子) */
c->rchild->parent=c;
c->parent=p;
if(c->rchild) /* c有右孩子(p原有右孩子) */
Status DeleteChild(BiPTree p,int LR) /* 形參T無用 */