一、数据结构和算法
数据结构:元素之间的关系,分为逻辑结构和存储结构。
1. 逻辑结构
(1)线性结构
每个元素前、后最多都只能有一个节点,如:线性表、栈、队列、数组、串
(2)非线性结构
如:二维数组、多维数组、树、图等
2. 存储结构
3. 顺序表
含有n个元素的线性表采用顺序存储,等概率删除其中任一个元素,平均需要移动
n
?
1
2
\frac{n-1}{2}
2n?1?个元素。
4. 链表
链表中的每个元素称为结点,每个结束是一个结构体变量,分为:
二、数组和字符串
数组:表示n个数据类型相同的元素所组成的序列。 字符串:字符构成的一维数组。
- 空串: 没有任何字符
- 空白串:字符都是不可见的
- 子串:串中任意个连续的字符组成的子序列
- 非平凡子串:非空且不同于字符串本身
- 串的模式匹配:模式串在主串中首次出现的位置
- 字符串的比较:从左到右按ASCIID码值进行比较
三、矩阵
1. 特殊矩阵
- 三角矩阵:上三角矩阵、下三角矩阵(存非零元素即可)。
- 对角矩阵
- 对称矩阵:
A
i
j
=
A
j
i
A_{ij}=A_{ji}
Aij?=Aji?
- 反对称矩阵:
A
i
j
=
?
A
j
i
A_{ij}=-A_{ji}
Aij?=?Aji?
2. 非特殊矩阵
3. 矩阵乘法
三、栈和队列
1. 队列(先进先出,FIFO——first in first out)
2. 栈(先进后出,FILO——first in last out)
四、树
1. 树的基本概念
- 父结点
- 子结点
- 兄弟结点
- 叶子结点:没有子结点
- 结点的度:结点有几个子结点
- 树的度:所有结点度最大的值
- 二叉树:树的度不超过2
- 层(深度、高度)
2. 二叉树
(1)一些概念
满二叉树
每一层都不能增加结点
完全二叉树
除了最后一层,都不能增加结点。 最后一层左侧连续有,右侧连续无。
非完全二叉树
除了最后一层,都不能增加结点。但不满足最后一层左侧连续有、右侧连续无的条件 。
(2)二叉树的特性
- 每一层上最多有
2
n
?
1
2^{n-1}
2n?1个节点
- 深度为k的二叉树最多有
2
k
?
1
2_k-1
2k??1个节点
- 如果对一棵有n个结点的完全二叉树的结点按层序编号,有:
如果i=1,则结点i无父结点,二叉树的根;如果i>1,则父结点是 i/2取整。 如果2i>n,则结点i为叶子结点,无左子结点;否则,其左子结点是结点2i。 如果2i+1>n,则结点i无右子结点,否则,其右子结点是结点2i+1。
3. 树的遍历
4. 特殊二叉树
(1) 二叉排序树
值最小的结点无左子树 值最大的结点无右子树。 二叉查找树的中序遍历序列为从小到大排列的序列 每一层从左到右进行遍历的序列为从小到大排列的序列。
(2) 哈夫曼树
- 树的路径长度:从树根到树中每一结点的长度之路
- 权:在一些应用中,赋予树中结点的一个有特定含义的数
- 带权路径长度:结点到树根之间的路径长度与该结点上权的乘积
- 树的代价:树的带权路径长度
- 哈夫曼树的构造
五、图
1. 图的分类
(1)有向图
一个顶点到另一个顶点是有方向的;
(2)无向图
(3)连通图
任意两个顶点之间都有一个路径相连。
- 强连通:有方向的情况下都连通
- 弱连通:不考虑方向的连通
(4)完全图
- 在无向图中,若每对顶点之间都有一条边相连,则为完全图。
- 在有向图中,若每对顶点之间都有两条有向连互连,则为完全图。
n个顶点的无向图和有向图的完全图边的个数计算:
- 无向图:
n
?
(
n
?
1
)
2
\frac{n*(n-1)}{2}
2n?(n?1)?
- 有向图:
n
?
(
n
?
1
)
n*(n-1)
n?(n?1)
2. 图的转换:无向图转邻接矩阵
用一个n阶的方阵R来存放图中各结点的关联信息。
无向图转换的邻接矩阵为对称矩阵。
有向图转邻接矩阵
无穷大表示无连接。
3. 度
(1)入度
指向该结点的边的数量
(2)出度
从该结点指出去的边的数量。
4. 有向图转邻接链表
六、算法特性与复杂度
1. 算法的特性
2. 算法评价指标
3. 时间复杂度
常见的算法时间复杂度:
O
(
1
)
<
(
l
o
g
2
n
)
<
O
(
n
)
<
O
(
n
l
o
g
2
n
)
<
O
(
n
2
)
<
O
(
n
3
)
<
O
(
2
n
)
O(1) < (log_2n) <O(n) <O(nlog_2n) <O(n^2)<O(n^3)<O(2^n)
O(1)<(log2?n)<O(n)<O(nlog2?n)<O(n2)<O(n3)<O(2n)
- 二分查找:
l
o
g
2
n
log_2n
log2?n
- 一次循环: O(n)
- 插入、冒泡、选择排序:
O
(
n
2
)
O(n^2)
O(n2)
4. 空间复杂度
临时空间占用大小。
七、查找
1. 顺序查找
从头到尾依次查找。 平均查找长度:
n
+
1
2
\frac{n+1}{2}
2n+1?
2. 二分查找(折半查找)
- 前提:有序表、顺序表。
- 折半出现小数时向下取整。
- 折半查找的时间复杂度为
O
(
l
o
g
2
n
)
O(log_2n)
O(log2?n)
- 折半查找成功时关键字的比较次数最多为
l
o
g
2
n
+
1
log_2n+1
log2?n+1次。
(1)二分法查找循环算法
int biSearch(int r[], int low, int high, int key){
int mid;
while(low<=high ){
mid = (low+high)/2;
if(key == r[mid]) return mid;
else if(key<r[mid]) high = mid-1;
else low = mid+1;
}
return -1;
}
int main(){
int data[] = {1,2,3,4,5};
int low=0;
int high=sizeof(data) /sizeof(int);
int key=3;
int find = biSearch(data, low, high, key);
printf("result: %d", find);
}
(2)二分法排序递归算法
int biSearchRecursion(const int r[], int low, int high, int key){
int mid;
if(low<=high){
mid = (low + high) /2;
if(key ==r[mid]) return mid;
else if(key<r[mid]) return biSearchRecursion(r, low, mid-1, key);
else return biSearchRecursion(r, mid+1, high, key);
}
return -1;
}
int main(){
const int data[] = {1,2,3,4,5};
int low=0;
int high=sizeof(data) /sizeof(int);
int key=3;
int find = biSearchRecursion(data, low, high, key);
printf("result: %d", find);
return 0;
}
3. 散列表查找:
(1)线性探查法
- 示例散列函数:
h
=
k
e
y
h=key%7
h=key
- 冲突解决:放在后面最近的空格里
(2)拉链法
仍使用散列函数,用链地址法。按关键码构造链,链的数量与关键码一致。
八、排序
1. 基本概念
- 稳定排序:相同值排序后保持前后序号
- 不稳定排序:相同值排序后序号不保持原来顺序
2. 插入类排序
(1) 直接插入排序
插入的元素与现有元素挨个比较,每次从元素前面一个元素比较,比前一元素小的话就继续往前比较。 直接插入排序在元素基本有序时比较有优势。
代码示例:
void insertSort(int data[], int n){
int i,j;
int tmp;
for(i=1;i<n;i++){
if(data[i]<data[i-1]){
tmp = data[i];
data[i] = data[i-1];
for(j=i-2;j>=0 && data[j]>tmp;j--){
data[j+1]=data[j];
}
data[j+1]=tmp;
}
}
}
int main(){
int data[] = {3,4,5,3,5,1,22};
insertSort(data, 7);
for(int i=0;i<7;i++){
printf("%d=%d\n", i, data[i]);
}
return 0;
}
(2)希尔排序
不稳定排序
3. 交换类排序
(1)冒泡排序
相邻元素比较和交换,一趟排序后最大移到最右、或最小移到最左。
示例代码:
#include <stdio.h>
int less(int x,int y){
return ((x<y)?1:0);
}
int large(int x, int y){
return ((x>y)?1:0);
}
void bubbleSort(int arr[], int n, int(*compare)(int,int)){
int i,j;
int swapped =1;
for(i=0;swapped;i++){
swapped=0;
for(j=0;j<n-1;j++){
if(compare(arr[j+1],arr[j])){
swap(arr[j+1], arr[j]);
swapped = 1;
}
}
}
}
void bubbleSortTest(){
int data1[] = {4,2,6,3,1};
bubbleSort(data1, 5, less);
for(int i : data1){
printf("%d ", i);
}
}
int main() {
bubbleSortTest();
return 0;
}
(2)快速排序
代码:
int qusort(int s[],int start,int end)
{
int i,j;
i=start;
j = end;
s[0]=s[start];
while(i<j)
{
while(i<j&&s[0]<s[j])
j--;
if(i<j)
{
s[i]=s[j];
i++;
}
while(i<j&&s[i]<=s[0])
i++;
if(i<j)
{
s[j]=s[i];
j--;
}
}
s[i]=s[0];
if (start<i)
qusort(s,start,j-1);
if (i<end)
qusort(s,j+1,end);
return 0;
}
4. 选择类排序
(1)直接选择排序
void selectSort(int data[], int n){
int i,j,k;
int temp;
for(i=0;i<n-1;i++){
for(k=i,j=i+1;j<n;j++){
if(data[j]<data[k])k=j;
if(k!=i){
temp = data[i];
data[i]=data[k];
data[k]=temp;
}
}
}
}
(2) 堆排序
- 大顶堆:越往上越大,父亲节点大于孩子结点;
- 小顶堆:越往下越大,孩子结点大于父亲结点;
5. 归并排序
6. 基数排序
7. 总结
类别 | 排序方法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
---|
插入排序 | 直接插入 |
O
(
n
2
)
O(n^2)
O(n2) |
O
(
n
2
)
O(n^2)
O(n2) |
O
(
1
)
O(1)
O(1) | 稳定 | 插入排序 | Shell排序 |
O
(
n
1.25
)
O(n^{1.25})
O(n1.25) |
?
-
? |
O
(
1
)
O(1)
O(1) | 不稳定 | 选择排序 | 直接选择排序 |
O
(
n
2
)
O(n^2)
O(n2) |
O
(
n
2
)
O(n^2)
O(n2) |
O
(
1
)
O(1)
O(1) | 不稳定 | 选择排序 | 堆排序 |
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2?n) |
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2?n) |
O
(
1
)
O(1)
O(1) | 不稳定 | 交换排序 | 冒泡排序 |
o
(
n
2
)
o(n^2)
o(n2) |
O
(
n
2
)
O(n^2)
O(n2) |
O
(
1
)
O(1)
O(1) | 稳定 | 交换排序 | 快速排序 |
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2?n) |
O
(
n
2
)
O(n^2)
O(n2) |
O
(
l
o
g
2
n
)
O(log_2n)
O(log2?n) | 不稳定 | | 归并排序 |
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2?n) |
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2?n) |
O
(
n
)
O(n)
O(n) | 稳定 | | 基数排序 |
O
(
d
(
r
+
n
)
)
O(d(r+n))
O(d(r+n)) |
O
(
d
(
r
+
n
)
)
O(d(r+n))
O(d(r+n)) |
O
(
r
+
n
)
O(r+n)
O(r+n) | 稳定 |
|