<福州集训之旅Day3> | 数据结构I |

目录

基础数据结构 I

基础数据结构

链表

队列

初级数据结构算法

Hash


<更新提示>

<第一次更新>主要内容为:特点与性质,用法,及功能代码,不附例题


<正文>

链表

特点与性质:用来存放一系列数据,但不像数组有必然存在的前后顺序关系,而是专门记录每个数据的前后是哪个数据,把所有数据联系起来。

常用实例:在数据总额确定,但部分数据大小极不稳定是,可以用于存储数据,节约大量空间。

功能代码:
存储:使用3个数组存储,D[]数组存储数据实际值,next[]数组存储在D[]中同下标数据在链表中的下一个数据位置,first[]数组用于存储各个链表第一个数据节点的位置。

遍历元素:
1 读取第一个数据下标
对数据操作后读取下一个位置的下标
3 继续向后访问,直至链表结束标志

//假设遍历k链表
for(i=first[k];i!=0;i=next[i])
{/*对D[i]的一些操作*/}

插入元素:
1 找到插入前的一个数据a
2 把插入元素x的后一个下标改为插入前一个元素a的后一个下标
3 把a数据的后一个下标改为x的下标

扫描二维码关注公众号,回复: 1892391 查看本文章
//设数据a,x的下标分别为px,pa
next[px]=next[pa];
next[pa]=px;

删除元素:
1 找到删除前的一个元素a
2 把a数据的后一个下标改为删除元素x的后一个下标
3 把删除元素下的后一个下标改为空

//设数据x,a的下标分别为px,pa
next[pa]=next[px];
next[px]=0;

拓展:
反向链表:将普通链表的链表头和每个数据下一个位置下标的记录改为链表尾和每一个数据上一个位置下标的记录。分别用prer[]数组和last[]数组存储。
双向链表:保护普通链表与反向链表的功能与结构。
循环链表:环形记录链表数据,最后一个数据的下一个位置的下标即链表首个位置的下标。

特点与性质:一种先进后出的容器,即先存入栈的元素必须在后入栈的元素取出后才能取出。DFS的递归即栈的思想。

功能代码:
存储:用一个数组存储栈,数组的大小即栈的大小。用一个top记录栈顶数据的下标。

栈的空满判定:
栈空时:top=0
栈满时:top=MAXSIZE-1

栈顶元素的访问:s[top]

元素x入栈:s[++top]=x

栈顶元素出栈:--top

队列

特点与性质:一种先进先出的容器,即后存入队的元素必须在先入栈的元素取出后才能取出。BFS的存数即队列的思想。

功能代码:
存储:用一个数组存储队列,数组的大小即队列的大小。用两个变量head和tail记录队列头元素下标-1和尾元素的下标。

队列的空满判定:
队列空时:head=tail
队列满时:tail=MAXSIZE-1

访问队首元素:q[head+1]

元素x入队q[++tail]=x

队头元素出队++head

拓展:
循环队列:利用废弃空间重新储存!
这里写图片描述

双端队列:队头可以进队(–head),队尾可以出队(–tail)。

特点与性质:堆就是个“父亲值总比儿子值大”或“父亲值总比儿子值小”的完全二叉树。即堆为{k1,k2,k3…}时,它满足:
当i=1,2,3,….,n/2时,k[i]<=k[2i]且k[i]<=k[2i+1] (小根堆)
或当i=1,2,3,….,n/2时,k[i]>=k[2i]且k[i]>=k[2i+1] (大根堆)

常用实例:堆在取序列最值上有初始的表现:在已经生产好的堆上取值的复杂度仅为O(1),修改的复杂度仅为O(logn),初始化也仅要O(nlogn)的数据。

功能代码:
取最值:H[1] (根据定义,堆的根节点即最大最小值)

调整:以小根堆为例
1 调整前先检查,与它的两个儿子做比较,如果比儿子小则不要调整,如果比儿子大则与儿子互换。
2 若去较小的儿子互换,则以儿子为跟的二叉树据不一定时堆了,对以儿子为根的二叉树继续递归操作。

void heapify(int x)
{
    int child=x*2;
    while(child<=N)
    {
        if(child<N&&H[child+1]<H[child])child++;
        if(H[x]>H[child])
        {
            swap(H[x],H[child]);
            x=child;
            child=x*2;
        }
        else break;
    }   
}

建堆:
先以叶节点为起始向根节点扫描,因为一个点本身就是堆,所以没向上扫描已经相当于加入了一个新的节点,再用刚刚的调整函数调整即可,只需要一直调整,就相当于建堆了。

void build()
{
    int i;
    for(int i=N/2;i>=1;i--)
    heapifl(i);
}

删除队首功能:
再堆中,删除堆尾元素很简单。所以,删除堆头就可以转化为删除堆尾,只要将堆尾元素移至堆首,将堆的规模减一即可。然后再在堆头做一次调整就能继续维护堆了。

void delete()
{
    if(N==1)N=0;
    else
    {
        H[1]=H[N--];
        heapify(1);
    }
}

插入新元素:以小根堆为例
插入元素同理,只需将新元素加入再堆底部,再调整,重建堆即可。


void insert(int key)
{
    int x=++N;
    while(x>1)
    {
        if(key<H[x/2])
        {
            H[x]=H[x/2];
            x/=2;
        }
        else break;
    }
    H[x]=key;
}

HASH

哈希即为一种映射算法,即有时候用数组储存数据时,数组的下标是不合法(如字符串)和不合适(极大的常数)的,我们需要一个函数对应一种转换算法,转换为一种合法合适的数组下标,便于我们计算使用。哈希就h是这种映射函数算法。能使得h[hash(k)]=d[k]。所有,我们也只是万不得已时才这么做。

注意!!一个优秀的哈希极力避免的是两个不同的数据映射后得到了同一个值,即f(k1)=f(k2)&&k1!=k2。

常用模型:
对于大数字k哈希采用的策略:除留余数法
f(k)=k mod p,p为一个满足一定具体需求和空间复杂度的大质数(107,100007等),f(k)即哈希结果。实际上,可以数学证明,p为大质数时,除留余数法重合的几率很小。

对于纯字母字符串k哈希采用的策略:按权展开法
可以把一个字母字符串看作26进制的数(大小写区分时则是52进制),将其按位对26乘次转化为一个十进制数,如果太大就继续套用除留余数法即可

虽然哈希尽量避免冲突,但有时冲突不可避免,冲突不可避免时,有两种解决策略:
线性地址开拓法:当当前地址已经填入时,向h[hash(k)+1],h[hash(k)+2],等往后开拓,并适当记录即可。
拉链法:再hash重复的每一个位置创建一个链表记录即可。


<后记>
保留了STL的一些容器模板的应用于代码,以后再补吧!


<废话>

猜你喜欢

转载自blog.csdn.net/prasnip_/article/details/79294650