文章目錄
跳躍表總結
解決了有序鏈表結構查找特定值困難的問題,查找特定值的時間複雜度爲O(logn),他是一種可以代替平衡樹的數據結構
問題
假如我們要用某種數據結構來維護一組有序的int型數據的集合,並且希望這個數據結構在插入、刪除、查找等操作上能夠儘可能着快速,那麼,你會用什麼樣的數據結構呢?
數組:
採用二分法可以在0(logn)的時間裏查找指定元素,但是插入和刪除不友好:查找0(logn),插入和刪除0(n)
鏈表:
查找0(n),但是插入和刪除就是0(1)
優化思路
如果我們現在查找元素爲9的節點,需要從頭節點遍歷8次。
我們增加一些路徑加快查找速度
現在我們遍歷5次就查找到了。
基於這種方法,對於具有n個元素的鏈表,我們採用logN+1層指針路徑的方法,可以實現在0(logn)查找到元素
調錶的搜索
例子:查找元素 117
(1) 比較 21, 比 21 大,往後面找
(2) 比較 37, 比 37大,比鏈表最大值小,從 37 的下面一層開始找
(3) 比較 71, 比 71 大,比鏈表最大值小,從 71 的下面一層開始找
(4) 比較 85, 比 85 大,從後面找
(5) 比較 117, 等於 117, 找到了節點。
我靠,java程序員表示真難懂!!!
//如果存在x,返回x所在的節點。否則返回x的後繼節點
find(x){
p=top; //p指向頭節點的最高層節點
while(1){ //不斷循環
while(p->next->key<x) //橫向查找滿足key>=x的p
p=p->next;
if(p-dowm==null){ //如果
return p-next;
}
p=p->down;
}
}
滿足redis標準的跳躍表數據結構
public class SkipList{
Node header,tail;
int level; //層數最大的節點
int length; //目前節點的個數
static class level{
Node next; //請進指針
int span; //跨度
}
static class Node implements Comparable { //帶有不同層高的節點
Object data=-1;
level[] levels; //存儲此節點鎖數的層數。每次創建時都會隨機生成一個32內隨機大小的level數組
double score; //按各個節點的分支大小從小到大排列
Node pre;
public Node(value,level){
this.value=value;
this.level=leve;
}
@Override
public int compareTo(Object o) {
return this.value > ((Node )o).value ? 1 : -1;
}
}
}
調錶的插入
先確定元素佔據的層數k(採用丟硬幣的方式)
然後在level數組中插入相關的元素
int random_level()
{
K = 1;
while (random(0,1)) //包括0,不包括1
K++;
return K;
}
跳躍表的性質
- 跳躍表由很多層組成,每一層都是一個有序列表
- 跳躍表最底層鏈表包含所有元素
- 如果一個元素出現在 Level i 的鏈表中,則它在 Level i 之下的鏈表也都會出現。
- 跳躍表是一種隨機化數據結構,通過拋硬幣決定層數
對比
- 對比二叉查找樹
因爲查找查找樹的插入、刪除、查找也是近似 O(logn) 的時間複雜度。
不過,二叉查找樹是有可能出現一種極端的情況的,就是如果插入的數據剛好一直有序,那麼所有節點會偏向某一邊。 - 紅黑樹
紅黑樹可以說是二叉查找樹的一種變形,紅黑在查找,插入,刪除也是近似O(logn)的時間複雜度
而且紅黑樹插入,刪除結點時,是通過調整結構來保持紅黑樹的平衡,比起跳躍表直接通過一個隨機數來決定跨越幾層,在時間複雜度的花銷上是要高於跳躍表的
自定義簡單跳躍表的實現-區別於redis的有序列表
自定義數據結構
public class SkipList{
Node header=new Node(-1,16); //跳躍表的頭節點
int levelMax=16; //允許最大層數
int length; //當前跳錶節點的個數
int levelCount=1; //當前跳躍表的層數,初始化爲1
static class level{ //層節點,相當於hashmap的entry
Node next; //指向下一個節點
int span; //跨越的層數,也就是levels數組的大小
public level(){};
public level(int span){
this.span=span;
this.next=new Node(span);
}
}
static class Node implements Comparable { //帶有不同層高的節點
int value=-1; //數據初始化爲-1,爲了簡便爲int類型
level[] levels; //存儲此節點的層節點。每次創建時都會隨機生成一個32內隨機大小的level數組,相當於hashmap的鏈表
public Node(value,span){
this.value=value;
this.span=span;
this.levels=new level(span);
}
@Override
public int compareTo(Object o) {
return this.value > ((Node )o).value ? 1 : -1;
}
}
跳躍表查詢方法
待優化,找到值還要一致向下遍歷,在levels[i]層找到,則在其下面各層肯定存在
public Node find(int value){
Node temp=header;
for(int levelCount-1;i>=0;i--){ //從最高層遍歷到最底層-縱向遍歷
while(temp.levels[i].next!=null && temp.levels[i].next.value<value){ //橫向遍歷,直到值不小於查找值 temp最終存放指定值的前驅
temp=temp.levels[i].next;
}
}
//遍歷所有層後,判斷是否找到 待定:應該上移判斷
if(temp.levels[i].next!=null && temp.levels[i].next.value==value){
System. out.println( value+ " 查找成功");
return temp.levels[i].next.value;
}
return null;
}
向跳躍表插入值,不允許重複
public void insert(int value){
int level =getLevel();
Node newNode = newNode(value, level); //創建一個新節點
Node[] update= newNode[level]; //記錄每一層插入位置的前驅節點
Node temp = head;
for(int i = level - 1; i >= 0; i--) { //找到每一層插入的前驅節點
while(temp.levels[i].next != null&& temp.levels[i].next.value< value) {
temp = temp.levels[i].next;
}
update[i] = temp;
}
//把插入節點的每一層連接起來
for( int i = 0; i < level; i++) {
newNode.levels[i].next = update[i].levels[i].next;
update[i].levels[i].next = newNode;
}
//判斷是否需要更新跳躍表的層數
if(level > levelCount) {
levelCount = level;
}
size++;
System. out.println( value+ " 插入成功");
}
}
}
在跳躍表刪除值
假設刪除值一定存在
public void delete(int value){
int level; //存儲刪除節點得層數
Node temp = head;
Node[] update= newNode[levelCount]; //此處大小設置爲levelCount!!
//1. 首先找到刪除值所有層得前驅位置
for(int i = levelCount- 1; i >= 0; i--) { //找到每一層插入的前驅節點
while(temp.levels[i].next != null&& temp.levels[i].next. value< value) {
temp = temp.levels[i].next;
}
update[i] = temp;
}
//2. 更新跳躍表得節點數
if(temp.levels[i].next != null&& temp.levels[i].next. value== value) {
size--;
}
System. out.println( value+ " 刪除成功");
//刪除節點後,連接鏈表
for(int i=0;i< levelCount;i++) {
if(update[i].levels[i].next != null&& update[i].levels[i].next.value== value) {
update[i].levels[i].next = update[i].levels[i].next.levels[i].next;
}
}
模擬拋硬幣
自己理解,和其他人優點區別
int getLevel() {
int level = (int)(Math.random() * (this.levelMax+1)); //隨機產生[0,1)的數字
System.out.println( "當前的level = "+ level);
return level;
}
隨機生成1-16的整數
方法1:使用Random類
Random r=new Random();
int a=r.nextInt(16)+1; //r.nextInt(16)會生成0-15
方法2:使用Math.random()
int num = (int) (Math.random() * 16 + 1); //Math.random()隨機生成[0,1)*16=[1,17) int[1,17)=[1,16]
總結
學習數據結構,不能只學習原理,一定要手動實現一遍
待更新。。。