1.简述什么是数据结构?
答:数据结构是计算机科学中的一个核心概念,主要包含了三个基本要素,逻辑结构、物理结构、数据集的额运算,数据结构的选择的选择设计对于算法的效率和程序的性能有这直接影响,合理的额数据结构能够使算法更高效,程序运行更快,资源消耗更小。
2.常见的数据结构有哪些?
答:常见的数据结构数组(Arry[ ]-连续内存区域)、链表(List-非连续内存区域)、栈(Stack-后进先出)、队列(Queue-先进先出)、树(Tree-分层数据)、图(Graph-节点+边)、堆(Heap-二叉树)、哈希表(Hash table)、集合(Set-无序且不重复)、映射(Map-键值对数据结构)
3.简述什么是链表,可以用于哪些场景?
答:链表是一种在物理内存上不连续的数据结构,其数据元素在逻辑上是线性有序的,每个节点包含元素本身的数据和指向下一个节点的指针。链表通过节点中存储的指向下一个节点的指针来实现节点之间的顺序结构,这使得他能在运行时动态的增加或者删除节点,且不需要分配固定的内存。
链表主要分为单向链表(包含指向后一个结点)、双向链表(包含指向前一个节点和后一个节点)、循环链表(头尾相连)、双向循环链表(头尾相连,可以指向前或后)、静态链表(数组模拟链表的行为)
链表可以用于动态内存管理(实现内存池)、实现队列和栈等抽象数据类型、表示图结构、文件系统、数据库索引等一系列场景
//单向链表
class ListNode
{
public:
int val;
ListNode *next;
ListNode(int x):val(x),next(NULL){};
};
//双向链表
class DoubleListNode
{
public:
int val;
DoubleListNode *next;
DoubleListNode *prev;
DoubleListNode(int x):val(x),next(NULL),prev(NULL){}
};
//循环链表,和单向链表相同
class CircularListNode
{
public:
int val;
CircularListNode *next;
CircularListNode(int x):val(x),next(NULL){}
};
//双向循环链表,和双向链表相同
class DoubleCircularListNode
{
public:
int val;
DoubleCircularListNode *next;
DoubleCircularListNode *prev;
DoubleCircularListNode(int x):val(x),next(NULL),prev(NULL){}
};
4.简述链表和数组(顺序结构)的区别是什么?
答:首先链表存储在非连续的内存中(堆),大小动态变化因此访问较慢且不能随机访问元素,需要遍历访问。而数据存储在连续的内存中(栈),大小在创建时就确定因此访问较快且通过索引可以随机访问元素。链表的插入和删除只需要改变节点指针额指向即可,而数组需要移动元素以保持连续性。
5.简述什么是栈,可以用于哪些场景?
答:栈(Stack)是一种线性的数据结构,其特点是只允许在一端进行插入和删除,因此后进先出。
栈的应用场景很多,譬如函数调用和内存管理(调用函数时局部变量等会压入栈)、表达式求职与解析(存储和操作词法单元)、递归算法的实现(通过栈来保存不同层次的函数调用状态,确保递归正确返回)、回溯算法(二叉树遍历使用,可用来保存当前状态和回退上一层)、深度优先搜索(DFS,树和图中,搜索更深的数据)。
//栈的递归调用
//n=4时,factorial(4)先压入栈中
//递归调用,4*factorial(3)压入栈中->return 4*3*2*1=24 弹出栈
//继续递归,3*factorial(2)压入栈中->return 3*2*1=6 弹出栈
//继续递归,2*factorial(1)先压入栈中->return 2*1=2 弹出栈
//factorial(1)压入栈中,结束递归->return 1 弹出栈
//最终返回24
unsigned long long int factorial(int n)
{
if(n==1||n==0)
{
return 1;
}
else
{
return n*factorial(n-1);
}
}
//栈回溯深度优先搜索八皇后问题
bool isSafe(vector<int> &queens,int row,int col)
{
for (size_t i = 0; i < row; i++)//随行遍历
{
if(queens[i]==col||queens[i]-i==col-row||queens[i]+i==col+row)//列相同或者对角线相同
{
return false;
}
}
return true;
}
void printsolution(const vector<int>&queens)
{
for (int i = 0; i < queens.size(); i++)
{
cout<<"("<<i<<","<<queens[i]<<")"<<endl;
}
cout<<endl;
}
void solveNQueens(int row,vector<int>&queens,stack<pair<int,int>>&statesStack)
{
if(row==queens.size())
{
printsoluton(queens);
return;
}
for (int col = 0; col < board.size(); ++col)
{
if (isSafe(board, row, col)) {
board[row] = col;
stateStack.push({row, col}); // 记录状态
solveNQueens(row + 1, board, stateStack);
stateStack.pop(); // 回溯,撤销选择
}
}
}
//使用栈的深度优先搜索算法前序遍历二叉树
void preorderTraversal(TreeNode *root)
{
if(root==nullptr) return;
stack<TreeNode*> stk;
stk.push(root);
while(!stk.empty())
{
TreeNode *node=stk.top();
stk.pop();//栈是后入先出的,单端增删
cout<<node->val<<" ";
if(node->right!=nullptr)
{
stk.push(node->right);
}
if(node->left!=nullptr)
{
stk.push(node->left);
}
}
}
6.你知道栈的内存是怎么分配的吗,栈溢出的的原因和解决方法有哪些?
答:栈的内存空间分配在程序编译时就已经完成了,栈中主要存储的是局部变量、函数参数和函数返回地址等。每当一个函数被调用时,编译器会为这个函数的参数和局部变量预留空间,当函数返回时,这部分空间自动释放。
栈溢出的原因主要有递归调用过深(递归每增加一层栈就会保存一层)、大量的局部变量保存、大尺寸的函数参数或返回等。当解决栈发生内存溢出的方法主要有递归逻辑优化,增加栈的大小等,或者使用动态内存分配。
7.队列是什么,队列和栈有什么区别,队列和栈的使用有什么不同?
答:队列(queue)和栈(stack)都是数据结构一种,和栈不同,他是先入先出的,而栈是后入先出,他可以被分为三部分,队头,队身和队尾其中入队发生在队尾,出队发生在队头,队列被用来处理一系列处理顺序与到达顺序相同的场景。
当queue.pop()时,最先放入的会被移出,而当stack.pop()时,最后放入的会被移除;队列在队头删除,队尾插入,而栈是在栈顶插入和删除。队列适合需要保持元素到达顺序的场景,即来一个新的,走一个旧的,而栈适用于回溯,DFL等算法应用,即来一个新的,在走一个旧的,位置还是那个位置。
8.谈谈你对堆的理解和堆的应用场景?
答:堆是一种特殊的数据结构,他是一个完全二叉树的表示。一般用于实现优先队列,堆可以分为最大堆和最小堆,最大堆父节点总大于或等于子节点,即根节点为最大值,最小堆父节点总是小于或等于子节点,即根节点为最小值,以这样的结构可以快速访问这个元素。
堆主要用来实现优先队列,优先队列是一种抽象数据,他可以像队列一样支持插入和删除,但是删除操作会删除最高优先级的元素,而不是像普通队列一样先进先出,堆可以实现排序操作,同时如果维护一个最大堆和最小堆,可以很方便的获取到数据流的中位数。
9.堆和普通二叉树有什么区别,和栈有什么区别?
答:堆虽然是是一个二叉树,但是他是一种特殊的完全二叉树,有最大、小堆要求,但是普通二叉树节点更自由,没有堆的特定顺序约束。
数据结构上堆和栈有原理性的不同,在内存管理上栈是自己管理,而堆事动态分配我们管理。