数据结构已学知识总结
数据结构已学知识总结
一、思维导图
二、重要概念的笔记
1、算法的时间复杂度分析
算法的时间复杂度可以通过原操作的执行次数来计算,通常if和else语句的时间复杂度为O(1),for循环语句的时间复杂度为O(n),所以一般情况下没有循环的算法中操作执行次数与问题规模无关,记作O(1),即常数阶,而含有循环的算法与问题规模n相关,例如具有双层for循环的算法时间复杂度一般为O(n^2),我们对算法的时间复杂度进行分析,有利于在碰到问题时找出更优的解法。
2、普通线性表
普通线性表的链式存储常分为头插法和尾插法。
头插法
void CreateListF(LinkList& L, int n) {
L = new LNode;
L->next = NULL;
int k;
for (int i = 0; i < n; i++) {
cin >> k;
LinkList p = new LNode;
p->next = L->next;
p->data = k;
L->next = p;
}
}
尾插法
void CreateListR(LinkList &L, int n){
L = new LNode;
L->next = NULL;
LinkList pre = L->next;
int k;
for (int i = 0; i < n; i++) {
cin >> k;
LinkList p = new LNode;
p->next = NULL;
p->data = k;
if(!pre)L->next = p;
else pre->next = p;
pre = p;
}
}
头插法的特点在于将输入数据逆序存储,可以用来实现链表的逆序以及栈的创建。
3、栈
栈的存储特点在于LIFO,即后进先出,可以借由C++中STL库的容器vector或者stack来实现。
我们用stack来进行栈的创建。
#include <iostream>
#include <stack>
using namespace std;
int main()
{
stack<int>ans;
return 0;
}
stack常用函数有:
ans.push(data)//将data压入栈中
data = ans.top()//取栈顶元素并赋值给data
ans.pop()//将栈顶元素压出
ans.empty()//判断栈是否为空,若空返回1, 若非空返回0
ans.size()//访问栈内元素个数
要注意的是,在对栈顶元素进行访问和删除时,要先判断栈是否为空,如果是以数组来实现栈,在入栈时还需要判断栈是否满。
4、队列
队列的存储特点在于FIFO,即先进先出,可以借由C++中STL库的容器queue来实现。
我们用queue来实现队列的创建。
#include <iostream>
#include <queue>
using namespace std;
int main()
{
queue<int>ans;
return 0;
}
queue常用函数有:
ans.push(data)//将元素data入队
data = ans.front()//访问队首元素并赋值给data
data = ans.back()//访问队尾元素并赋值给data
ans.pop()//将队首元素出队
ans.empty()//判断队列是否为空,若空返回1,非空返回0
ans.size()//访问队列规模
5、串
串即字符串,可以使用字符数组或者C++标准库自带的string实现
我们用string来创建与读写字符串。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str;
cin>>str;
getline(cin,str);
cout<<str;
return 0;
}
string类字符串str可以用cin和getline进行输入,区别在于cin遇到空格停止,而getline则在回车时停止。
串最常见的问题在于串的模式匹配问题,即在目标串中寻找子串的问题。我们有BF和KMP两种算法来解决。
BF算法顾名思义,是一种暴力算法,它的好处在于简单粗暴,易于操作,在目标串中只有少量模式串的子串的前提下,我们通常可以直接使用BF算法来解决。
void BF(string& s, string& t) {
int i = 0, j = 0;
while (i < s.length() && j < t.length()) {
if (s[i] == t[j]) {
i++;
j++;
}
else {
i = i - j + 1;
j = 0;
}
}
if (j >= t.length())cout << "yes";
else cout << "no";
}
KMP算法则是对模式串进行信息提取,从而降低整个算法的时间复杂度。
void kmp(string s, string t) {
int* next = new int(t.length());
getnext(t, next);
//getnextval(t, next);
int i = 0, j = 0;
while (i < s.length()) {
if (j == -1 || s[i] == t[j]) {
i++;
j++;
}
else j = next[j];
if (j == t.length()) {
cout << "yes" << endl;
j = 0;
}
}
}
void getnext(string t, int* next) {
next[0] = -1;
int i = 0, j = -1;
while (i < t.length()-1) {
if (j == -1 || t[i] == t[j]) {
j++;
i++;
next[i] = j;
}
else j = next[j];
}
}
void getnextval(string t, int* next) {
next[0] = -1;
int i = 0, j = -1;
while (i < t.length()-1) {
if (j == -1 || t[i] == t[j]) {
j++;
i++;
if (t[i] != t[j])next[i] = j;
else next[i] = next[j];
}
else j = next[j];
}
}
核心思想就是对模式串t进行预处理,提取它相同的字符数k,然后再进行匹配。
a | a | a | b | |
---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 |
next[i] | -1 | 0 | 1 | 2 |
nextval[i] | -1 | -1 | -1 | 2 |
三、疑难问题及解决方案
1、对于环形队列的理解问题:
问题描述:环形队列是一种特殊的队列,它解决了在判断是否队满的条件(rear==Maxsize-1)时存在队列仍有空位的情况,即假溢出的情况。我的问题在于使用数组来实现环形队列时不能很好地理解队首与队尾的取模问题。
解决方法:仔细翻阅了数据结构的教材,从利用书上的图例很好的理解了这个概念。环形队列的数组实现常用的方法是为队列留下一个空位,此时初始化front、rear为0,此时队空的条件是rear == front,而每次入队时,判断(rear+1)%Maxsize是否等于front,如果相等,说明队满。 而出队与入队的操作也有一定改变,即
front = (front+1)%Maxsize,rear = (rear+1)%Maxsize;