跳表比較好理解,但是實際用代碼來表示,還是有點復雜的。
實現的方法不唯一
1. 什么是跳表
跳表是鏈表+索引的一種數據結構 ,是以空間換取時間的方式,關于跳表參考:https://baike.baidu.com/item/跳表/22819833?fr=aladdin
2. 跳表概念
跳表在原有鏈表的基礎上,增加索引,從而可以進行二分查找,提高搜尋效率。
原始鏈表
Head ——> 1 ——> 8 ——> 12 ——> 23 ——> 55 ——> NULL
新增了索引的鏈表(跳表)
Head2 ————————> 8 ———————————————————————> NULL
Head1 ————————> 8 —————————> 23 —————————> NULL
Head0 ——> 1 ——> 8 ——> 12 ——> 23 ——> 55 ——> NULL
Head0 , Head1 , Head2 上都是真實的節點,這就是以空間換取時間
例如算上Head, 元素數據一共有 6 個,而添加索引后,元素一共有 11 個
3. 跳表增刪查規則
3.1 跳表數據節點
數據節點可以和鏈表節點一致 ,也可以定義如下節點,除了數據外,有指針指向 前一個/后一個/上一個/下一個 節點,以便后續查找操作。
typedef struct {
int data;
struct Node *next; // 后一個節點
struct Node *last; // 前一個節點
struct Node *up; // 上一個節點
struct Node *down; // 下一個節點
} Node;
3.2 跳表初始化
當跳表有多少層的時候,應當建立多少個頭結點,例如: 跳表為3層
Head2 ——> NULL
Head1 ——> NULL
Head0 ——> NULL
3.3 查找
刪除/新增 都會進行查詢才操作,無非是刪除/新增索引而已。
例如有如下數據
Head2 —————————————————————> 23 —————————> NULL
Head1 ————————> 8 —————————> 23 —————————> NULL
Head0 ——> 1 ——> 8 ——> 12 ——> 23 ——> 55 ——> NULL
要查找13這個節點
去除無效層
例如:Head2后面第一個節點的數據23, 而23大于13, 所以Head2沒有數據匹配查詢,故需要跳到下面一層,至Head1上進行查詢。
查詢至Head0層
去除無效層后數據進入了Head1, 在Head1上進行匹配,當匹配到23時,23大于13,將23標記為 查詢結束點,對23的上一個節點 8 進行 向下指針操作,進入Head0層的8節點。
查找實際數據
從Head0層的8 進行查找,直至查詢結束標記點(head1 23), 查詢的數據分別為 8 , 12 ,23 查詢結束,未找到數據。
3.4 新增
新增操作需要記錄索引尋址過程,以便后續新增索引。
頭結點插入
頭結點插入一定是去除無效層至Head0 , 且Head0的第一個節點都比插入節點要大的情況下
例如:
如下跳表,插入 2
Head2 —————————————————————> 23 —————————> NULL
Head1 ————————> 8 —————————> 23 —————————> NULL
Head0 ——> 3 ——> 8 ——> 12 ——> 23 ——> 55 ——> NULL
尾結點插入
頭結點插入一定是去除無效層至Head0 , 且Head0的第一個節點都比插入節點要小,直至NULL節點的情況下
例如:
如下跳表,插入 65
Head2 —————————————————————> 23 —————————> NULL
Head1 ————————> 8 —————————> 23 —————————> NULL
Head0 ——> 3 ——> 8 ——> 12 ——> 23 ——> 55 ——> NULL
中間節點插入
除開以上2種情況,其余情況為 中間節點插入
新增索引
拋硬幣的方法,當數據量達到一定規模的時候,一定是趨近于 50%的。
所以跳表會越來越趨向于如下形式
3
3 7
1 3 5 7 9
1 2 3 4 5 6 7 8 9
判斷是否需要新增索引,采取拋硬幣的方法來判斷,即: 隨機數 取余 為 0 則需要新增,否則不需要。
例如如下跳表,插入 65
Head2 —————————————————————> 23 —————————> NULL
Head1 ————————> 8 —————————> 23 —————————> NULL
Head0 ——> 3 ——> 8 ——> 12 ——> 23 ——> 55 ——> NULL
尋址應該為
Head2: 23
Head1: 23
元素數據插入后為
Head2 —————————————————————> 23 ———————————————> NULL
Head1 ————————> 8 —————————> 23 ———————————————> NULL
Head0 ——> 3 ——> 8 ——> 12 ——> 23 ——> 55 ——> 65 —> NULL
當插入65節點后,若判斷需要索引的時候,則先為 Head1 添加索引,添加位置為 尋址地址之后,寄 Head1: 23
Head2 —————————————————————> 23 ———————————————> NULL
Head1 ————————> 8 —————————> 23 —————————> 65 —> NULL
Head0 ——> 3 ——> 8 ——> 12 ——> 23 ——> 55 ——> 65 —> NULL
繼續判斷,若不需要添加索引,則插入結束
若還需要添加索引,則繼續上述操作,直至 索引層 達到最高層
3.5 刪除
刪除首先是查找操作【3.3 查找】
若未找到該節點,則刪除失敗
若找到了該節點,則應當提到該數據最高索引層,再從高到低刪除
例如:
如下跳表,刪除 23
Head2 —————————————————————> 23 ———————————————> NULL
Head1 ————————> 8 —————————> 23 —————————> 65 —> NULL
Head0 ——> 3 ——> 8 ——> 12 ——> 23 ——> 55 ——> 65 —> NULL
找到 Head0 23 后,應該向上找到 Head2 23 ,然后從高向低刪除,若刪除后,該索引沒有數據了,則索引層減1
則刪除Head2 23 后數據如下
Head1 ————————> 8 —————————> 23 —————————> 65 —> NULL
Head0 ——> 3 ——> 8 ——> 12 ——> 23 ——> 55 ——> 65 —> NULL
刪除Head1 23 后數據如下
Head1 ————————> 8 ———————————————————————> 65 —> NULL
Head0 ——> 3 ——> 8 ——> 12 ——> 23 ——> 55 ——> 65 —> NULL
刪除Head0 23后數據如下
Head1 ————————> 8 ————————————————> 65 —> NULL
Head0 ——> 3 ——> 8 ——> 12 ——> 55 ——> 65 —> NULL
4. 代碼
skipList.c
# include
# include
# include
int MaxLevel = 8; // 最大層數
int currLevel = 0; // 當前層數
// 數據節點
typedef struct {
int data;
struct Node *next;
struct Node *last;
struct Node *up;
struct Node *down;
} Node;
// 記錄索引尋址過程
typedef struct {
int level;
struct Node *node;
} skipStep;
// 判斷是否需要新增索引, 拋硬幣
bool randNum() {
if(0 == (rand() % 2))
return true;
return false;
}
// 新增節點
bool add(Node *SL[] , int data) {
printf("新增節點: %d
",data);
int level = currLevel;
Node *Head = NULL;
Node *tmp = NULL;
Node *last = NULL;
// 初始化索引 數據為 Head 地址
skipStep steps[MaxLevel];
int i;
for (i=0;i0;
steps[i].node = SL[i];
Node *ss = steps[i].node;
}
// 賽選無效層
Head = SL[level];
tmp = Head->next;
while ((level > 0) && (data < tmp->data)) {
level--;
Head = SL[level];
tmp = Head->next;
}
// 根據索引尋找Head0數據節點
while ((level > 0)) {
while (tmp != NULL) {
if (data < tmp->data) {
steps[level].level = level;
if (NULL != last)
steps[level].node = last;
tmp = last->down;
level--;
break;
}
last = tmp;
tmp = tmp->next;
}
if (NULL == tmp) {
steps[level].level = level;
if (NULL != last)
steps[level].node = last;
tmp = last->down;
level--;
}
}
// Head0 數據合適的節點
while (tmp != NULL) {
if (data < tmp->data) {
break;
}
last = tmp;
tmp = tmp->next;
}
// 新增節點
Node *newData = (Node *)malloc(sizeof(Node));
newData->data = data;
newData->up = NULL;
newData->down = NULL;
newData->last = NULL;
newData->next = NULL;
int k = 0;
// Head0 插入原始數據
if (NULL == last ) {
// 頭結點
Head = SL[0];
Node *headNext = Head->next;
if (NULL != headNext) {
newData->next = headNext;
headNext->last = newData;
newData->last = Head;
}
Head->next = newData;
newData->last = Head;
} else if ( NULL == tmp) {
// 尾節點
last->next = newData;
newData->last = last;
} else {
// 中間節點
newData->next = tmp;
tmp->last = newData;
newData->last = last;
last->next = newData;
}
// 構建索引
while (randNum()) {
k++;
if (k >= MaxLevel) break;
// 新增索引數據
Node *newIndex = (Node *)malloc(sizeof(Node));
newIndex->data = data;
newIndex->up = NULL;
newIndex->down = NULL;
newIndex->next = NULL;
newIndex->last = NULL;
// 建立上下級關系
newIndex->down = newData;
newData->up = newIndex;
Node *node = steps[k].node;
// node->next
Node *nextIndex = node->next;
node->next = newIndex;
newIndex->last = node;
newIndex->next = nextIndex;
if (NULL != nextIndex)
nextIndex->last = newIndex;
newData = newIndex;
// 判斷是否需要新增索引層數
if (k > currLevel)
currLevel = k;
}
}
// 初始化頭結點
Node *initSkipList(Node *skipList[]) {
int i;
for (i=0;imalloc(sizeof(Node));
if (NULL == newHead) {
printf("%d 層 頭結點申請失敗
");
return NULL;
}
newHead->data = -1-i;
newHead->down = NULL;
newHead->up = NULL;
newHead->next = NULL;
newHead->last = NULL;
skipList[i] = newHead;
}
return skipList;
}
// 打印跳表數據
void PrintSkipList(Node *SL[]) {
if (NULL == SL) {
return;
};
int level = currLevel;
//int level = MaxLevel;
int i;
for (i=level;i>=0;i--) {
Node *Head = SL[i];
Node *tmp = Head->next;
printf("第%d層 ",i);
while (NULL != tmp) {
printf(" %d ",tmp->data);
tmp = tmp->next;
}
printf("
");
}
}
// 查詢數據
Node *query(Node *SL[] , int data) {
printf("查詢數據: %d
",data);
int level = currLevel;
Node *Head = NULL;
Node *tmp = NULL;
Node *last = NULL;
Head = SL[level];
tmp = Head->next;
int endQuery = -1;
// 篩除無效層
while ((level > 0) && (data < tmp->data)) {
level--;
endQuery = tmp->data;
Head = SL[level];
tmp = Head->next;
}
// 根據索引定位到Head0層
while ((level > 0 )) {
while (tmp != NULL) {
if (data < (tmp->data)) {
level--;
endQuery = tmp->data;
tmp = last->down;
break;
}
last = tmp;
tmp = tmp->next;
}
if (NULL == tmp) {
tmp = last->down;
endQuery = -1;
level--;
}
}
// 查詢實際數據
while (NULL != tmp) {
if (endQuery != -1)
if (tmp->data > endQuery) {
tmp = NULL;
break;
}
if (tmp->data == data) {
break;
}
tmp = tmp->next;
}
// 返回查詢的數據節點,若沒有查詢到,應當返回NULL ,否則返回實際的地址
return tmp;
}
// 刪除數據
bool del(Node *SL[],int data) {
printf("刪除數據: %d
",data);
// 找到節點地址
Node *tmp = query(SL,data);
if (NULL == tmp) {
printf("未找到節點,刪除失敗
");
return false;
}
int level = 0;
Node *t_last = NULL;
Node *t_next = NULL;
// 找到該數據最高索引
while (NULL != tmp->up) {
level++;
tmp = tmp->up;
}
// 由上至下刪除索引/數據
while (tmp != NULL) {
t_last = tmp->last;
t_next = tmp->next;
Node *t_down = tmp->down;
if (t_last == NULL) {
printf("上一個節點不可能為空,刪除失敗,層數: %d
",level);
return false;
}
t_last->next = t_next;
if (NULL != t_next)
t_next->last = t_last;
else
t_last->next = NULL;
if ((t_last == SL[level]) && (NULL == t_next)) {
currLevel--;
}
free(tmp);
tmp = t_down;
level--;
}
return true;
}
int main() {
Node *SL[MaxLevel];
Node *skipList = initSkipList(SL);
if (NULL == SL) {
printf("skipList 申請失敗
");
return -1;
}
// 測試新增
int num[] = {1,3,2,10,8,9,22,30,29,120,99,78,55,76,21};
int i;
for (i=0;i<sizeof(num)/sizeof(int);i++) {
add(skipList,num[i]);
}
PrintSkipList(SL);
// 測試刪除
int delNum[] = {99,9,78,55,3,1,28,78};
for (i=0;i<sizeof(delNum)/sizeof(int);i++) {
del(skipList,delNum[i]);
}
PrintSkipList(SL);
printf("
");
return 0;
}
;i++)>;i++)>
執行結果
# gcc skipList.c -w -g
# ./a.out
新增節點: 1
新增節點: 3
新增節點: 2
新增節點: 10
新增節點: 8
新增節點: 9
新增節點: 22
新增節點: 30
新增節點: 29
新增節點: 120
新增節點: 99
新增節點: 78
新增節點: 55
新增節點: 76
新增節點: 21
第5層 99
第4層 99
第3層 76 99
第2層 9 76 99
第1層 3 9 29 30 76 78 99
第0層 1 2 3 8 9 10 21 22 29 30 55 76 78 99 120
刪除數據: 99
查詢數據: 99
刪除數據: 9
查詢數據: 9
刪除數據: 78
查詢數據: 78
刪除數據: 55
查詢數據: 55
刪除數據: 3
查詢數據: 3
刪除數據: 1
查詢數據: 1
刪除數據: 28
查詢數據: 28
未找到節點,刪除失敗
刪除數據: 78
查詢數據: 78
未找到節點,刪除失敗
第3層 76
第2層 76
第1層 29 30 76
第0層 2 8 10 21 22 29 30 76 120
#
審核編輯:湯梓紅
-
編程
+關注
關注
88文章
3637瀏覽量
93989 -
數據結構
+關注
關注
3文章
573瀏覽量
40232 -
鏈表
+關注
關注
0文章
80瀏覽量
10599
原文標題:讓你的代碼更加優雅的編程技巧-跳轉表
文章出處:【微信號:技術讓夢想更偉大,微信公眾號:技術讓夢想更偉大】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
外部FLASH的數據要如何進行增刪查改?
跳表的基本概念和實際應用
![<b class='flag-5'>跳表</b>的基本<b class='flag-5'>概念</b>和實際應用](https://file.elecfans.com/web2/M00/13/19/pYYBAGEwPCSAUEYWAAAPOi5qjNk443.png)
評論