??xml version="1.0" encoding="utf-8" standalone="yes"?>
跌表(Skip ListQ是1987q才诞生的一U崭新的数据l构Q它在进行查找、插入、删除等操作时的期望旉复杂度均为O(logn),有着q乎替代q树的本领。而且最重要的一点,是它的~程复杂度较同类的AVL树,U黑树等要低得多Q这使得其无论是在理解还是在推广性上Q都有着十分明显的优ѝ?/font>
首先Q我们来看一下蟩跃表的结?/font>
跌表由多条链构成(S0QS1QS2 ……QShQ,且满_下三个条Ӟ
每条铑ֿd含两个特D元素:+∞ ?-∞(其实不需?
S0包含所有的元素Qƈ且所有链中的元素按照升序排列?br />
每条链中的元素集合必d含于序数较小的链的元素集合?br />
操作
一、查?br />
目的Q在跌表中查找一个元素x
在蟩跃表中查找一个元素xQ按照如下几个步骤进行:
1. 从最上层的链QShQ的开头开?br />
2. 假设当前位置为pQ它向右指向的节点ؓqQp与q不一定相邻)Q且q的gؓy。将y与x作比?br />
(1) x=y 输出查询成功及相关信?br />
(2) x>y 从p向右Ud到q的位|?br />
(3) x<y 从p向下Ud一?/font>
3. 如果当前位置在最底层的链中(S0Q,且还要往下移动的话,则输出查询失?/font>
二、插?br />
目的Q向跌表中插入一个元素x
首先明确Q向跌表中插入一个元素,相当于在表中插入一列从S0中某一位置出发向上的连l一D元素。有两个参数需要确定,x入列的位|以及它?#8220;高度”?br />
关于插入的位|,我们先利用蟩跃表的查扑֊能,扑ֈ比x的最大的数y。根据蟩跃表中所有链均是递增序列的原则,x必然插在y的后面?br />
而插入列?#8220;高度”较前者来说显得更加重要,也更加难以确定。由于它的不定性,使得不同的决{可能会D截然不同的算法效率。ؓ了插入数据之后Q保持该数据l构q行各种操作均ؓO(logn)复杂度的性质Q我们引入随机化法QRandomized AlgorithmsQ?/font>
我们定义一个随机决{模块,它的大致内容如下Q?/font>
产生一??的随机数r r ← random()
如果r于一个常数pQ则执行ҎAQ?nbsp; if r<p then do A
否则Q执行方案B else do B
初始时列高ؓ1。插入元素时Q不停地执行随机决策模块。如果要求执行的是A操作Q则列的高度加1Qƈ且l反复执行随机决{模块。直到第iơ,模块要求执行的是B操作Q我们结束决{,q向跌表中插入一个高度ؓi的列?/font>
我们来看一个例子:
假设当前我们要插入元?#8220;40”Q且在执行了随机决策模块后得到高度ؓ4
步骤一Q找到表中比40的最大的敎ͼ定插入位置
步骤二:插入高度?的列Qƈl护跌表的l构
三、删?/p>
目的Q从跌表中删除一个元素x
删除操作分ؓ以下三个步骤Q?/p>
在蟩跃表中查扑ֈq个元素的位|,如果未找刎ͼ则退?
该元素所在整列从表中删除
多余的“I链”删除
我们来看一下蟩跃表的相兛_杂度Q?br />
I间复杂度: O(n) Q期望)
跌表高度: O(logn) Q期望)
相关操作的时间复杂度Q?br />
查找Q?nbsp; O(logn) Q期望)
插入Q?nbsp; O(logn) Q期望)
删除Q?nbsp; O(logn) Q期望)
之所以在每一后面都加一?#8220;期望”Q是因ؓ跌表的复杂度分析是Z概率论的。有可能会生最坏情况,不过q种概率极其微小?/p>
--------------------------------------------------------------------------------
以下是自己学习时到的一些问?/p>
首先分配一个链表,用list.hdr指向Q长度ؓ跌表规定的最高层Q说是链表,在以下代码中只是分配了一D连l的I间Q用来指向每一层的开始位|。我们看到结构体nodeType中,有一个key,一个rec(用户数据)Q还有一个指向结构体的指针数l?/p>
一开始的那些囑֮易给解。如上图所C,例如每个节点的forward[2]Q就认ؓ是蟩跃表的第3层。List.hdr的forward[2]指向11Q?1的forward[2]指向30Q?0的forward[2]指向53。这是跌表的W?层:11---30-----53。(准确的说每个forward都指向新节点Q新节点的同层forward又指向另一个节点,从而构成一个链表,而数据只有一个,q不是像开始途中所ȝ那样有N个副本)。本人天资愚钝,看了挺长旉才把它在内存里的l构看清楚了Q呵c?nbsp;
以下是在|上搜到的一个实C?/p>
代码中主要注释了insert函数Q剩下的两个函数差不多,׃一一注释?/p>
view plaincopy to clipboardprint?
/* skip list */
#include <stdio.h>
#include <stdlib.h>
/* implementation dependent declarations */
typedef enum {
STATUS_OK,
STATUS_MEM_EXHAUSTED,
STATUS_DUPLICATE_KEY,
STATUS_KEY_NOT_FOUND
} statusEnum;
typedef int keyType; /* type of key */
/* user data stored in tree */
typedef struct {
int stuff; /* optional related data */
} recType;
#define compLT(a,b) (a < b)
#define compEQ(a,b) (a == b)
/* levels range from (0 .. MAXLEVEL) */
#define MAXLEVEL 15
typedef struct nodeTag {
keyType key; /* key used for searching */
recType rec; /* user data */
struct nodeTag *forward[1]; /* skip list forward pointer */
} nodeType;
/* implementation independent declarations */
typedef struct {
nodeType *hdr; /* list Header */
int listLevel; /* current level of list */
} SkipList;
SkipList list; /* skip list information */
#define NIL list.hdr
static int count = 0;
statusEnum insert(keyType key, recType *rec) {
int i, newLevel;
nodeType *update[MAXLEVEL+1];
nodeType *x;
count++;
/***********************************************
* allocate node for data and insert in list *
***********************************************/
/* find where key belongs */
/*从高层一直向下寻找,直到q层指针为NILQ也是?nbsp;
后面没有数据了,到头了,q且q个g再小于要插入的倹{?nbsp;
记录q个位置Q留着向其后面插入数据*/
x = list.hdr;
for (i = list.listLevel; i >= 0; i--) {
while (x->forward[i] != NIL
&& compLT(x->forward[i]->key, key))
x = x->forward[i];
update[i] = x;
}
/*现在让X指向W?层的X的后一个节?/
x = x->forward[0];
/*如果相等׃用插入了*/
if (x != NIL && compEQ(x->key, key))
return STATUS_DUPLICATE_KEY;
/*随机的计要插入的值的最高level*/
for (
newLevel = 0;
rand() < RAND_MAX/2 && newLevel < MAXLEVEL;
newLevel++);
/*如果大于当前的levelQ则更新update数组q更新当前level*/
if (newLevel > list.listLevel) {
for (i = list.listLevel + 1; i <= newLevel; i++)
update[i] = NIL;
list.listLevel = newLevel;
}
/* l新节点分配I间Q分配newLevel个指针,则这?nbsp;
节点的高度就固定了,只有newLevel。更高的层次?nbsp;
不会再有q个?/
if ((x = malloc(sizeof(nodeType) + newLevel*sizeof(nodeType *))) == 0)
return STATUS_MEM_EXHAUSTED;
x->key = key;
x->rec = *rec;
/* l每层都加上q个|相当于往链表中插入一个数*/
for (i = 0; i <= newLevel; i++) {
x->forward[i] = update[i]->forward[i];
update[i]->forward[i] = x;
}
return STATUS_OK;
}
statusEnum delete(keyType key) {
int i;
nodeType *update[MAXLEVEL+1], *x;
/*******************************************
* delete node containing data from list *
*******************************************/
/* find where data belongs */
x = list.hdr;
for (i = list.listLevel; i >= 0; i--) {
while (x->forward[i] != NIL
&& compLT(x->forward[i]->key, key))
x = x->forward[i];
update[i] = x;
}
x = x->forward[0];
if (x == NIL || !compEQ(x->key, key)) return STATUS_KEY_NOT_FOUND;
/* adjust forward pointers */
for (i = 0; i <= list.listLevel; i++) {
if (update[i]->forward[i] != x) break;
update[i]->forward[i] = x->forward[i];
}
free (x);
/* adjust header level */
while ((list.listLevel > 0)
&& (list.hdr->forward[list.listLevel] == NIL))
list.listLevel--;
return STATUS_OK;
}
statusEnum find(keyType key, recType *rec) {
int i;
nodeType *x = list.hdr;
/*******************************
* find node containing data *
*******************************/
for (i = list.listLevel; i >= 0; i--) {
while (x->forward[i] != NIL
&& compLT(x->forward[i]->key, key))
x = x->forward[i];
}
x = x->forward[0];
if (x != NIL && compEQ(x->key, key)) {
*rec = x->rec;
return STATUS_OK;
}
return STATUS_KEY_NOT_FOUND;
}
void initList() {
int i;
/**************************
* initialize skip list *
**************************/
if ((list.hdr = malloc(
sizeof(nodeType) + MAXLEVEL*sizeof(nodeType *))) == 0) {
printf ("insufficient memory (initList)\n");
exit(1);
}
for (i = 0; i <= MAXLEVEL; i++)
list.hdr->forward[i] = NIL;
list.listLevel = 0;
}
int main(int argc, char **argv) {
int i, maxnum, random;
recType *rec;
keyType *key;
statusEnum status;
/* command-line:
*
* skl maxnum [random]
*
* skl 2000
* process 2000 sequential records
* skl 4000 r
* process 4000 random records
*
*/
maxnum = 20;
random = argc > 2;
initList();
if ((rec = malloc(maxnum * sizeof(recType))) == 0) {
fprintf (stderr, "insufficient memory (rec)\n");
exit(1);
}
if ((key = malloc(maxnum * sizeof(keyType))) == 0) {
fprintf (stderr, "insufficient memory (key)\n");
exit(1);
}
if (random) {
/* fill "a" with unique random numbers */
for (i = 0; i < maxnum; i++) key[i] = rand();
printf ("ran, %d items\n", maxnum);
} else {
for (i = 0; i < maxnum; i++) key[i] = i;
printf ("seq, %d items\n", maxnum);
}
for (i = 0; i < maxnum; i++) {
status = insert(key[i], &rec[i]);
if (status) printf("pt1: error = %d\n", status);
}
for (i = maxnum-1; i >= 0; i--) {
status = find(key[i], &rec[i]);
if (status) printf("pt2: error = %d\n", status);
}
for (i = maxnum-1; i >= 0; i--) {
status = delete(key[i]);
if (status) printf("pt3: error = %d\n", status);
}
return 0;
}
本文来自CSDN博客Q{载请标明出处Qhttp://blog.csdn.net/topcoder1234/archive/2010/08/26/5841119.aspx
思考:对于q个问题,我们可以暴力地来解决,从a[i]一直篏加到a[j],最坏的情况下复杂度为O(n),对于mơchange&querry,合v来的复杂度ؓO(m*n),在n或m很大的情况下,q样的复杂度是让人无法忍受的.另外,如果没有元素的变?我们完全可以存储sum[1,k](k=1,2,……),然后对Q意给定的查找区间[i,j],都可以方便的用ans=sum[1,j]-sum[1,i-1],当然q只是没有元素改变的情况下的比较优化的解?那么对于有元素变更的问题是否有更高效的方法呢?(废话!没有我还写啥?!)可以想一?每次更改的元素是比较的,有时候甚xơ只改变一个元?但是在用暴力Ҏ求区间和的时?却对区间内所有的元素都篏加了一?q样其实造成了许多无谓的q算.q时候也怼惛_如果能把一些结果存h会不会减很多运?{案是肯定的,但问题是怎么?存什?如果存Q意区间的?n比较大的时候不但内存吃不消,而且存储的量太大,不易更改,反而得不偿?那么也许可以考虑存储特定的一些区?比如说线D|,其实现在讨论的问题用U段树完全可以解,以后再详l写U段?.那么现在重新回过头来,看下q个问题,我们已经定了要存储一些特定区间sum的想?接下来我们要解决的无非是两个问题:1、减更改元素后对这些区间里的sum值的更改旉.2、减查扄旉.
好了废话了这么半?无非是想让自׃及看到的人明白ؓ什么要用树状数l?
接下来正式入?
首先我们可以借鉴元素不变更问题的优化Ҏ,先得到前i-1之和and前j之?以s[i]表示前i之?那么sum[i,j]=s[j]-s[i-1].那么现在的问题已l{化ؓ求前i之和了.另外,我们已经定要存储一些特定区间的?现在p来揭C些特定的区间I竟指什?
在文字说明之前先引入一个非常经典的,在网上找到的树状数组文章里几乎都要出现的一个图?/p>
从图中不隑֏?c[k]存储的实际上是从k开始向前数k的二q制表示中右边第一?所代表的数字个元素的和(q么说可能有Ҏ?令lowbit为k的二q制表示中右边第一?所代表的数?然后c[k]里存的就是从a[k]开始向前数lowbit个元素之?q么存有什么好处呢?无论是树状数l还是线D|,都用C分块的思想,而树状数l采用这L存储l构我想最主要的还是这h便计?我们可以用位q算L地算出lowbit.分析一下这样做的复杂度:对于更改元素来说,如果Wi个元素被修改?因ؓ我们最l还是要求和,所以可以直接在c数组里面q行相应的更?如图中的例子,假设更改的元素是a[2],那么它媄响到得c数组中的元素只有c[2],c[4],c[8],我们只需一层一层往上修改就可以?q个q程的最坏的复杂度也不过O(logN);对于查找来说,如查找s[k],只需查找k的二q制表示?的个数次p得到最l结?比如查找s[7],7的二q制表示中有3?,也就是要查找3?到底是不是呢,我们来看上图,s[7]=c[7]+c[6]+c[4],可能你还不知道怎么实现q个q程.
q以7Z,二进制ؓ0111,双W一?出现在第0位上,也就是说要从a[7]开始向前数1个元?只有a[7]),即c[7];
然后这?舍掉,得到6,二进制表CZؓ0110,双W一?出现在第1位上,也就是说要从a[6]开始向前数2个元?a[6],a[5]),即c[6];
然后舍掉用过?,得到4,二进制表CZؓ0100,双W一?出现在第2位上,也就是说要从a[4]开始向前数4个元?a[4],a[3],a[2],a[1]),即c[4].
代码实现:
数据l构是对在计机内存中(有时在磁盘中Q的数据的一U安排。数据结构包括数l、链表、栈、二叉树、哈希表{等。算法对q些l构中的数据q行各种处理。例如,查找一条特D的数据ҎҎ据进行排序?/font>
掌握q些知识以后可以解决哪些问题呢?
现实世界数据存储
E序员的工具
建模
数据l构的特性:
数组Q优Ҏ插入快,如果知道下标Q可以非常快地存取。缺Ҏ查找慢,删除慢,大小固定?/font>
有序数组Q优Ҏ比无序的数据查找快。缺Ҏ删除和插入慢Q大固定?/font>
栈:优点是提供后q先出方式的存取。缺Ҏ存取其他很慢?/font>
队列Q提供先q先出方式的存取。缺Ҏ存取其他很慢?/font>
链表Q优Ҏ插入快,删除快。缺Ҏ查找慢?/font>
二叉树:优点是查找、插入、删除都快(如果树保持^衡)。缺Ҏ删除法复杂?/font>
U-黑树Q查找、插入、删除都快。树Lq的。缺Ҏ法复杂?/font>
2-3-4树:优点是查找、插入、删除都快。树Lq的。类似的树对盘存储有用。缺Ҏ法复杂?/font>
哈希表:优点是如果关键字已知则存取极快。插入快。缺Ҏ删除慢,如果不知道关键字则存取很慢,对存储空间用不充分?/font>
堆:优点是插入、删除快Q对最大数据项的存取很快。缺Ҏ对其他数据项存取慢?/font>
图:优点是对现实世界建模。缺Ҏ有些法且复杂?/font>
对于大多数数据结构来_都需要知道如何插入一条新的数据项Q如何寻找某一特定的数据项Q如何删除某一特定的数据项Q还需要知道如何P代地讉K某一数据l构中的各数据项Q以便进行显C或其他操作。另一U重要的法范畴是排序?/font>
一、通用数据l构Q数l,链表Q树Q哈希表
它们被称之ؓ通用的数据结构是因ؓ它们通过关键字的值来存储q查找数据,q一点在通用数据库程序中常见刎ͼ栈等Ҏl构正好相反Q它们只允许存取一定的数据)?/font>
通用数据l构可以完全按照速度的快慢来分类Q?/font>
数组和链表是最慢的Q树相对较快Q哈希表是最快的?/font>
但ƈ不是使用最快的l构永远是最好的Ҏ。这些最快的l构也有~陷Q首先,它们的程序在不同E度上比数组和链表的复杂;其次Q哈希表要求预先知道要存储多数据,数据对存储空间的利用率也不是非常高。普通的二叉树对序的数据来_会变成缓慢的O(N)U操?而^衡树虽然避免了上q的问题Q但是它的程序编制v来却比较困难?/font>
数组在下列情况下很有用:
数据量较?/font>
数据量的大小事先可预?/font>
如果存储I间_大的话,可以放松W二条,创徏一个够大的数l来应付所有可以预见的数据输入?/font>
如果插入速度很重要的话,使用无序数组。如果查N度很重要的话,使用有序数组Qƈ用二分查找。数l元素的删除L很慢Q这是由于ؓ了填充空出来的单元,q_半数以上的数l元素要被移动。在有序数组中的遍历是很快的Q而在无序的数l不支持q种功能?/font>
向量Q如Java中的向量c)是一U当数据太满时可以自己扩充空间的数组。向量可以应用于数据量不可预知的情况下。然而,在向量扩充时Q要旧的数据拷入一个新的空间中Q这一q程会造成E序明显的周期性暂停?/font>
如果需要存储的数据量不能预知或者需要频J地插入删除数据元素Ӟ考虑使用链表。当有新的元素加入时Q链表就开辟新的所需要的I间Q所以它甚至可以占满全部可用内存;在删除过E中没有必要像数l那h?#8220;I洞”?/font>
二、专用数据结构:栈,队列Q优先队列
三、排序:插入排序Q希排序,快速排序,归ƈ排序Q堆排序
四、图Q邻接矩阵,L?/font>
五、外部存储:序存储Q烦引文ӞB-树,哈希Ҏ
本文来自CSDN博客Q{载请标明出处Qhttp://blog.csdn.net/adcxf/archive/2008/08/06/2775636.aspx
先让我们来验证下q个巧妙的方法准性,来算?的^Ҏ (Computed by Mathomatic)
1-> x_new = ( x_old + y/x_old )/2 y (x_old + -----) x_old #1: x_new = --------------- 2 1-> calculate x_old 1 Enter y: 2 Enter initial x_old: 1 x_new = 1.5 1-> calculate x_old 2 Enter y: 2 Enter initial x_old: 1 x_new = 1.4166666666667 1-> calculate x_old 3 Enter y: 2 Enter initial x_old: 1 x_new = 1.4142156862745 1-> calculate x_old 10 Enter y: 2 Enter initial x_old: 1 Convergence reached after 6 iterations. x_new = 1.4142135623731 ...
可见Q随着q代ơ数的增加,q算g愈发接近真实倹{很奇的算法,可是怎么来的? 查了?a title="Wikipedia: Newton's method" >wikipedia?a title="Wolfram: Newton's Iteration" >wolframQ原来算法的名字叫Newton’s Iteration (牛顿q代??/p>
下面是极?span lang="ja" xml:lang="ja">つまらな?/span>(boring)的数理介l,不喜Ƣ数学的a下之意也是l大部分人可以略q了?/p>
假设f(x)
是关?code class="math">X的函?
求出f(x)
的一阶导Q即斜率:
化等式得?
然后利用得到的最l式q行q代q算直至求到一个比较精的满意|Z么可以用q代法呢?理由是中值定?Intermediate Value Theorem):
如果
f
函数在闭区间[a,b]
内连l,必存在一?code class="math">x使得f(x) = c
Q?code class="math">c是函?code class="math">f在闭区间[a,b]
内的一?
我们先猜一X
初始|例如1Q当然地球h都知道除?本n之外M数的qx栚w不会?。然后代入初始|通过q代q算不断推进Q逐步靠近_|直到得到我们主观认ؓ比较满意的gؓ止。例如要?68的^ҎQ因?code class="math">252 = 625Q?code class="math">302 = 900Q我们可先代入一猜测?6Q然后P代运,得到较精?27.7128?/p>
回到我们最开始的那个”莫名其妙”的公式,我们要求的是N
的^ҎQox2 = n
Q假设一关于X
的函?code class="math">f(x)?
f(X) = X2 - n
?code class="math">f(X)的一阶导?
f'(X) = 2X
代入前面求到的最l式?
Xk+1 = Xk - (Xk2 - n)/2Xk
化简卛_到我们最初提到的那个求^Ҏ的神奇公式了:
我之前介l过?em>The Art and Science of C一书中有用?a title="The Art and Science of C 阅读W记 II" >泰勒公式求^Ҏ的算?/a>Q其实牛P代法也可以看作是泰勒公式(Taylor Series)的简化,先回下泰勒公式:
仅保留等式右边前两项:
?code class="math">f(X0+ε) = 0Q得?
再oX1 = X0 + ε0
Q得?code class="math">ε1…依此cL可知:
转化?
从推导来看,其实牛顿q代法不仅可以用来求qx根,q可以求立方根,甚至更复杂的q算?/p>
同样Q我们还可以利用C语言来实C那个最单的求^Ҏ的公?管我们可以直接?code>sqrt()完成)
#include <stdio.h> #include <math.h> #define N 768 main() { float x=1; int i; for (i=1;i<=1000;i++) { // recursion times : 1000 x = (x + N/x)/2; } printf("The square root of %d is %f\n",N,x); }