数据结构是程序员的“内功”,根据不同场景的需要,选择合适的数据结构来设计代码。数据结构的定义是:相互之间存在一种或多种特定关系的数据元素的集合。常用的数据结构有线性表、单向链表、双向链表、栈、堆、二叉树、二叉排序树、红黑树、图等。
线性表的顺序存储结构,指的是用一段地址连续的存储单元一次存储线性表的数据元素。
优点:
缺点:
在指定位置插入元素
先在链表中找到你要插入位置的节点,将待插入节点的next指向当前节点的next,再将当前节点的next指向待插入节点。
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinkList p,s;
p=*L;
j=1;
while(p&&j<i)
{
p=p->next;
++j;
}
if(!p||j>i)
return ERROR;
s=(LinkList)malloc(sizeof(Node));
s->data=e;
s->next=p->next;
p->next=s;
return OK;
}
删除指定位置元素
先找到指定位置的节点的前一个节点,前一个节点的next指向待删除节点的next,再释放待删除节点。
Status ListDelete(LinkList *L,int i,ElemType *e)
{
int j;
LinkList p,q;
p=*L;
j=1;
while(p->next&&j<i)
{
p=p->next;
++j;
}
if(!p->next||j>i)
return ERROR;
q=p->next;
p->next=q->next;
free(q);
return OK;
}
双向链表比单向链表每个节点多了一个前驱的指针,插入删除操作要更复杂一些。
在指定位置插入节点
先找到指定位置的节点,将待插入节点的prior指向当前节点,将待插入节点的next指向当前节点的next,将当前节点的next的prior指向待插入节点,将当前节点的next指向待插入节点。
简单来说就是,先接待插入的前和后,在接待插入两边节点的后和前。
Status ListInsert(LinkList *L,int i,ElemType e)
{
int j;
LinkList p,s;
p=*L;
j=1;
while(p&&j<i)
{
p=p->next;
++j;
}
if(!p||j>i)
return ERROR;
s=(LinkList)malloc(sizeof(Node));
s->data=e;
s->prior=p;
s->next=p->next;
p->next->prior=s;
p->next=s;
return OK;
再指定位置删除节点
先找到制定位置的节点,将待删除节点的prior的next指向待删除节点的next,再将待删除节点的next的prior待删除节点的prior。
Status ListDelete(LinkList *L,int i,ElemType *e)
{
int j;
LinkList p,q;
p=*L;
j=1;
while(p->next&&j<i)
{
p=p->next;
++j;
}
if(!p->next||j>i)
return ERROR;
p->prior->next=p->next;
p->next->prior=p->prior;
free(q);
return OK;
}
栈是限定仅在表尾进行插入和删除操作的线性表。特性是先进后出。
队列是只允许在一端进行插入操作,在另一端进行删除操作的线性表。特性是先进先出。
经典题目,不可错过
首先这个组合的队列要有两个栈,另外还要记录最后插入的元素。
插入时就插入到栈A中,顺便记录插入元素。
弹出时,栈B非空的话就从栈B弹出,否则就把A的所有元素压入栈B,再从栈B弹出。
获取头部元素,从栈B获取头部元素,如栈B为空,就把栈A的所有元素都压入栈B,再获取栈B头部元素。
获取尾部元素,直接从记录的最后插入元素获取。
简单来说,插入操作交给栈A,弹出操作交给栈B,栈B为空就把栈A的元素都搬到栈B。
class Queue{
stack<int> stackA;
stack<int> stackB;
int back_elem;
public:
void push(int elem){
stackA.push(elem);
back_elem=elem;
}
void pop(){
if(!stackB.empty()){
stackB.pop();
}else if(!stackA.empty()){
while(!stackA.empty(){
stackB.push(stackA.top());
stackA.pop();
}
stackB.pop();
}else{
//error
}
}
int front(){
if(!stackB.empty())
return stackB.top();
else if(!stackA.empty()){
while(!stackA.empty()){
stackB.push(stackA.top());
satckA.pop();
}
return stackB.top();
}else{
//error
}
}
int back(){
if(!empty())
return back_elem;
else
//error
}
int size(){
return stackA.size()+stackB.size();
}
bool empty(){
return stackA.empty()&&stackB.empty();
}
}
同样经典的题目
两个队列的作用是一样的,而且总是 保持其中一个为空,另一个非空。
新push进来的元素总是插入到非空队列中,空队列则用来保存pop操作之后的那些元素,那么此时空队列不为空了,原来的非空队列变为空了,总是这样循环。
class my_stack{
queue<int> queue1;
queue<int> queue2;
public:
void push(int elem){
if(!qeueue1.empty())
queue1.push(elem);
else if(!queue2.empty())
queue2.push(elem);
else
queue1.push(elem);
}
void pop(){
if(queue1.empty()){
while(queue2.size()>1){
queue1.push(queue2.front());
queue2.pop();
}
queue2.pop()
}else{
while(queue1.size()>1){
queue2.push(queue1.front());
queue1.pop();
}
int &data=queue1.front();
queue1.pop();
return data;
}
}
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<=K2i+2 ,则称为小堆(或大堆)。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
//先序遍历二叉树
void PreOrder(BiTree T)
{
stack<BiTree> S;
BiTreeNode* p=T;
while (p|| !S.empty())
{
if (p)
{
printf("%c",p->data);
S.push(p);
p = p->LChild;
}
else
{
p=S.top();
S.pop();
p = p->RChild;
}
}
}
//中序遍历二叉树
void InOrder(BiTree T)
{
stack<BiTree T> S;
BiTreeNode* p=T;
while (p || !S.empty())
{
if (p)
{
S.push(p);
p = p->LChild;
}
else
{
p=S.top();
S.pop();
printf("%c", p->data);
p = p->RChild;
}
}
}
//后序遍历
void Postorder(BiTree T)
{
stack<BiTree> S;
BiTreeNode* p=T;
char tag[Maxsize] = {'0'};
while (p || !StackEmpty(*S))
{
if (p)
{
S.push(p);
tag[S->top] = '0';//标志结点是否遍历右子树
p = p->LChild;
}
else
{
while (tag[S->top] == '1') {
p=S.top();
S.pop();
printf("%c",p->data);
}
if (S->top == -1) break;
p=S.top();
S.pop();
S.push(p);
p = p->RChild;
tag[S->top] = '1';
}
}
}
二叉搜索树虽然可以提高我们查找数据的效率,但如果插入二叉搜索树的数据是有序或接近有序的,此时二叉搜索树会退化为单支树,在单支树当中查找数据相当于在单链表当中查找数据,效率是很低下的。
树的左右子树都是AVL树。
树的左右子树高度之差(简称平衡因子)的绝对值不超过1。
如果一棵二叉搜索树的高度是平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g N ) ,搜索时间复杂度也是 O ( l o g N )
虽然AVL数有很强的平衡性,但是当有频繁的插入删除时,会需要大量的旋转保持平衡,使得效率低下。红黑树是一种特殊的二叉查找树,大致平衡,方便插入删除。
红黑树有以下特征
1、红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
2、平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。
是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。
直接定址法、数字分析法、除留余数法、随机数法。
处理哈希冲突
有两种主要的方法:一个是开放寻址法,一个是拉链法。
最好让你的实力与你的野心匹配,否则将一事无成。少抱怨,多做事,着眼于当下,不要过于怀旧,也不用过于期待。