栈
-
定义
-
栈是指限制在表尾进行删除和增加的线性表。
线性表
是指有序的元素的序列,第一个元素无前驱,最后一个元素没有后继,中间的元素有并且只有一个前驱和后继。线性表分为链式存储的和顺序存储两种来实现。 -
数据结构是怎么实现的
顺序存储
:用数组来实现
#include<iostream>
using namespace std;
#define SIZE 100
typedef struct {
int top;
int elements[SIZE];
}stack;
bool full(stack* s) {
if(s->top == (SIZE-1))
{
return true;
}
return false;
}
bool empty(stack* s) {
s->top == -1?true:false;
}
int getSize(stack* s) {
return SIZE-(s->top)-1;
}
int geteleNum(stack* s) {
return s->top+1;
}
int top(stack* s) {
return s->elements[s->top];
}
bool push(stack* s, int ele) {
if(!full(s))
{
//栈顶指针加1,将新的元素赋给新的栈顶。
s->top++;
s->elements[s->top] = ele;
return true;
}
return false;
}
void pop(stack* s) {
if(!empty(s))
{
// 将栈顶元素删除 top--
s->elements[s->top] = 0
s->top--;
}
}
void init(stack* s) {
s->top = -1;
}
int main()
{
stack s;
init(&s);
for(int i=0;i<10;i++)
{
push(&s,i);
}
int temp = geteleNum(&s);
for(int i=0;i<temp;i++)
{
cout<<top(&s)<<endl;
pop(&s);
}
return 0;
}
链式存储
:用链表来实现
#include<iostream>
using namespace std;
#define ElementType int
typedef struct Node{
ElementType ele;
Node* pNext;
}Node;
typedef struct{
int count;
Node* pTop;
}stack;
bool empty(stack* s) {
s->count == 0 ?true:false;
}
int top(stack* s) {
return s->pTop->ele;
}
bool push(stack* s, int ele) {
Node* p = new Node;
p->ele = ele;
p->pNext = s->pTop;
s->pTop = p;
s->count++;
return true;
}
void pop(stack* s) {
if(!empty(s))
{
Node* p = s->pTop;
s->pTop = s->pTop->pNext;
delete p;
s->count--;
}
}
void init(stack* s) {
s->count = 0;
s->pTop = nullptr;
}
int main()
{
stack s;
init(&s);
for(int i=0;i<10;i++)
{
push(&s,i);
}
for(int i=0;i<10;i++)
{
cout<<top(&s)<<endl;
pop(&s);
}
return 0;
}
-
基本的操作
push pop top getsize empty init clear destroy操作等等 -
特点
先入栈的参数后出栈 这是最大的特点。 -
应用场景
-
栈的主要应用主要在于递归操作,这里有一个经典的例子就是斐波那契数列。代码如下:
#include<iostream>
using namespace std;
int fobo(int n){
if(0 == n) {
return 0;
}else if(1 == n) {
return 1;
}else {
return fobo(n-1) + fobo(n-2);
}
}
int main()
{
for(int i=0; i<10; i++)
{
cout<<fobo(i)<<endl;
}
return 0;
}
-
word的撤销键,浏览网页的时候的退回原来的网页等等都是利用了栈这种数据结构。
-
和其他数据结构的差别
队列
-
定义
-
队列是只允许在一端进行插入操作,在另外一端进行删除操作的线性表。
-
数据结构是怎么实现的
顺序存储
链式存储
代码如下
#include<iostream>
using namespace std;
#define int eleType;
typedef struct Node{
eleType ele;
Node* pNext;
};
typedef struct queue{
Node* head;
Node* tail;
};
bool push(queue* q,eleType data)
{
Node* p = new Node;
p->ele = data;
p->pNext = nullptr;
q->tail->pNext = p;
q->tail = q->tail->pNext;
return true;
}
bool pop(queue* q,eleType data)
{
if(q->head == q->tail)
{
return false;
}
Node* p = q->head->pNext;
q->head = p->pNext;
if(q->tail == p)
{
q->tail = q->head;
}
delete p;
}
int main()
{
return 0;
}
- 特点
先进去的数据先出来,后进去的数据后出来。 - 应用场景
类似于排队机制,鼠标点击,当电脑阻塞的时候,会把阻塞的鼠标操作依次执行。 - 和其他数据结构的差别
堆
这块要注意一个点,操作系统中的堆栈和数据结构中的堆栈不是一个概念,操作系统的堆栈是系统分配的物理内存,而数据结构中的堆栈是抽象的数据结构
-
定义
-
堆是一种特殊的树形的数据结构,是基于完全二叉树实现的,堆可以分为大根堆和小根堆,大根堆的父节点要大于子节点,小根堆的父节点要小于子节点。
-
数据结构是怎么实现的
-
基于完全二叉树实现的。可以是数组的形式,也可以是链表的形式。
-
特点
所以的非叶子节点都满足 key【i】>key【2i+1】key【i】>key【2i+2】或者是key【i】<key【2i+1】key【i】<key【2i+2】让父节点最大或者最小。 -
应用场景
-
大小根堆用于排序,海量数据问题当中,大根堆可以排序找出top十个最小的,小跟堆可以排序找出十个最大的。
大根堆可以用作优先队列
,只有数字大的元素可以先出来,数字小的元素没有办法先出来。 -
堆排序算法
代码如下
#include<iostream>
using namespace std;
void swap(int arr[],int a, int b)
{
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
//调整方法
void adjust(int arr[], int s, int size)
{
int temp = arr[s];
//左孩子的下标 右孩子的下标是2*s+2
for(int i = 2*s +1; i<=size; i*=2)
{
//找左右孩子中的最大
if(i<size && arr[i] <arr[i+1])
i++;
//比较父节点和较大孩子谁大,如果父节点大,不做调整break;如果孩子节点大,那么交换
if(temp > arr[i])
break;
arr[s] = arr[i];
s = i;
}
arr[s] = temp;
}
//大顶堆
void Sort(int arr[], int size)
{
for(int i = size/2; i>=0; i--)
{
adjust(arr,i,size);
}
//把堆顶的元素(最大的元素)和最后一个元素交换
for(int i=size; i>0; i--)
{
swap(arr,0,i);
//将剩下的元素构成一个大顶堆
adjust(arr,0,i-1);
}
}
int main()
{
int arr[6] = {23,43,5,67,87,45};
Sort(arr,5);
//adjust(arr,0,5);
for(auto i:arr)
{
cout<<i<<endl;
}
return 0;
}
双向链表
- 定义
- 数据结构是怎么实现的
- 特点
- 应用场景
- 和其他数据结构的差别
单向链表
- 定义
结点分为数据域和指针域(指针域中存放下一个结点的地址),这种结点链成的一个链表,称之为单链表。 我们把链表的第一个存储位置叫做链表的头指针。 - 数据结构是怎么实现的
typedef struct listNode{
elementType data;
listNode* next;
} listNode;
typedef listNode *linkList ;
- 特点
链式存储的,结点个数不受限制,可以随便扩充,对于增加和删除,时间复杂为o(1),对于查找,时间复杂度为o(n)。 - 应用场景
链表的基本操作
插入节点
bool linkInsert(listNode* head, int i, int ele)
{
listNode* pcurrent = head;
int j = i;
//判断节点是否存在在链表之间
while(pcurrent != NULL && j>1)
{
pcurrent = pcurrent->next;
j--;
}
if(j>1)
{
return false;
}
listNode* temp = new listNode;
temp->data = ele;
temp->next = pcurrent->next;
pcurrent->next = temp;
return true;
}
删除节点
bool delete(listNode* head, int i)
{
//判断节点是否在链表中 并且要找到删除节点的前面一个节点
listNode* pcurrent = head;
int j = i-1;
while(pcurrent != NULL && j>1)
{
pcurrent = pcurrent->next;
j--;
}
if(j>1)
{
return false;
}
listNode* temp = pcurrent->next;
pcurrent->next = temp->next;
delete temp;
return true;
}
- 和其他数据结构的差别
最大的区别就是和顺序存储的线性表的区别,顺序存储的线性表的插入和删除的时间复杂度都是o(n),但是对于查找的复杂度是o(1)。
关于链表有很多的面试题 这里整理了一部分错题
链表的反转 (思路 遍历链表 ,每次把节点插入到新链表的第一个!)
listNode* reverse(listNode* head)
{
if(head == NULL || head->next == NULL)
{
return head;
}
listNode* pnewHead = NULL;
listNode* pcurrent = head;
while(pcurrent != NULL)
{
listNode* ptemp = pcurrent;
pcurrent = pcurrent->next;
ptemp->next = pnewHead;
pnewHead = ptemp;
}
return pnewHead;
}
寻找链表的第k个节点 (注意边界条件,注意循环的次数,走到第四个节点只需要走三步)
listNode* find(listNode* head, int k)
{
if(head == NULL && k== 0)
{
return NULL;
}
listNode* front = head;
listNode* behind = head;
int i = k;
while(i>1 && front != NULL)
{
i--;
front = front->next;
}
//如果走到链表末尾,但是i还没减减结束,那么说明k是大于链表长度的。
if(i>1)
{
return NULL;
}
while(front != NULL)
{
front = front->next;
behind = behind->next;
}
return behind;
}
寻找链表的中间节点
listNode* findMiddle(listNode* head)
{
if(head == NULL || head->next == NULL || head->next->next == NULL)
{
return NULL;
}
listNode* front = head;
listNode* behind = head;
while(front != NULL)
{
front = front->next;
//偶数的情况下只能再走一步。
while(front != NULL)
{
front = front->next;
}
behind = behind->next;
}
return behind;
}
二叉树
- 定义
二叉树的父节点最多两颗字数,左孩子树和右孩子树是有顺序的,不能颠倒。
特殊的二叉树
斜树
分为左斜树和右斜树 左斜树是指所有节点都只有左孩子树,右斜树是指所有的节点都只有右孩子树。
满二叉树
也可以叫做完美二叉树
,所有的节点都有左子树和右子树,并且所有的叶子节点在都在同一层上面,这种我们叫做满二叉树。
完全二叉树
堆就是基于完全二叉树实现的 - 数据结构是怎么实现的
顺序存储(一般只用于完全二叉树)和链式存储(二叉链表) - 特点
- 应用场景
- 和其他数据结构的差别
图
- 定义
- 数据结构是怎么实现的
- 特点
- 应用场景
- 和其他数据结构的差别
哈希
- 定义
哈希函数(散列函数)
:散列技术是指在记录的存储位置和它的关键字之间建立一个确定的关系f,使得每个关键字key对应一个存储位置f(key)。
哈希表
:采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或者哈希表
理想状态下,通过散列函数计算出来的地址是不一样的,但是现实当中可能出现冲突,我们把出现冲突的两个不同的key叫做同义词
。 - 数据结构是怎么实现的
(1)连续的存储空间 数组实现 会出现元素重复到同一个地址空间的情况 这个时候采用线性探测
和二次探测
的方法来存放冲突的元素。
(2)数组加链表的实现(STL中的实现)类似于下图 这个叫做开链法
完成的哈希表。
- 特点
- 应用场景
海量数据问题处理
高效率的查找 - 和其他数据结构的差别
hashtable没有自动排序的功能,红黑树有,map里面的元素也是有序的
查找的效率是o(1),因为直接定位到地址。