数据结构(C语言版本)
第1章 绪论
1.常用的数据结构类型:集合、线性、树形、图状。
2.数据结构:
- 逻辑结构:数据元素之间的关系
- 存储结构:数据结构在计算机中的表示。存储结构分为:顺序存储结构和链式存储结构。
3.算法是对特定问题求解步骤的一种描述,算法具有如下特性:有穷性、确定性、可行性、输入、输出。
4.算法的度量:
- 时间复杂度
- 空间复杂度
第二章 线性表
1.线性表的定义:
- 存在唯一一个“第一个”元素
- 存在唯一一个“最后一个”元素
- 除第一个元素外,每一个元素都有且只有一个前驱
- 除最后一个元素外,每个元素都有且只有一个后继
2.线性表合并的方法:
- 将表A中的元素逐个和B中的比较,不同就加入到B中。时间复杂度为O(len(A)*len(B))
- AB是有序排列的前提下,使用两个指针分别指向A和B,将两者较小的元素插入到C中,然后移动较小的指针,直到全部插入到C。时间复杂度为O(len(A)+len(B))
3.线性表的顺序实现:满足LOC(A(i+l))=LOC(A(i))+l,LOC表示元素地址。特点:下标读取快O(1),插入删除O(n)。
#include <stdlib.h>
#ifndef SEQUENCE_LINER_LIST_H
#define SEQUENCE_LINER_LIST_H
/*
线性表的顺序实现
*/
#define LIST_INIT_SIZE 100 //初始分配量
#define LIST_INCREMENT 10 //增量
#define LIST_TYPE int //存储类型
typedef struct strSequenceLinerList
{
LIST_TYPE *elem; //存储空间基址
unsigned int length; //当前长度
unsigned int listSize; //当前存储容量
}SequnceLinerList;
static enum Status
{
OK,
FAILED,
OVERFLOW
};
/* 线性表初始化 */
Status initSequenceLinerList(SequnceLinerList& list)
{
list.elem = (LIST_TYPE *)malloc(sizeof(LIST_TYPE)*LIST_INIT_SIZE);
if (!list.elem)
{
return OVERFLOW;
}
list.length = 0;
list.listSize = LIST_INIT_SIZE;
return OK;
}
/* 扩展 */
Status resizeSequenceLinerList(SequnceLinerList& list)
{
printf("Resize...\n");
list.elem = (LIST_TYPE*)realloc(list.elem, sizeof(LIST_TYPE)*(list.listSize + LIST_INCREMENT));
if (!list.elem)
{
return OVERFLOW;
}
list.listSize += LIST_INCREMENT;
return OK;
}
/* 打印 */
void printSequnceLinerList(SequnceLinerList& list)
{
printf("Begin print list...\n");
printf("length=%d,listSize=%d.\n",list.length,list.listSize);
unsigned int i = 0;
for (; i < list.length-1;++i)
{
printf("%d,",list.elem[i]);
}
printf("%d\n", list.elem[i]);
printf("End print list.\n");
}
/* 线性表插入:position表示插入的位置,从1开始。第一个元素是list.elem[0] */
Status insertSequenceLinerList(SequnceLinerList& list, unsigned int position, LIST_TYPE value)
{
if (position<1 || position>list.length+1)
{
return FAILED;
}
if (list.length >= list.listSize)
{
Status res = resizeSequenceLinerList(list);
if (OK != res)
{
return FAILED;
}
}
LIST_TYPE* p = &list.elem[position -1];
LIST_TYPE* q = &list.elem[list.length];
for (; p != q; --q)
{
*(p + 1) = *p;
}
list.length++;
*p = value;
return OK;
}
/* 删除元素 */
Status deleteSequenceLinerList(SequnceLinerList& list, unsigned int position)
{
if (position<1 || position>list.length)
{
return FAILED;
}
LIST_TYPE* p = &list.elem[position - 1];
LIST_TYPE* q = &list.elem[list.length - 1];
for (; p != q; ++p)
{
*p = *(p+1);
}
--list.length;
return OK;
}
/* 查找:第一个值的下标 */
unsigned int findSequenceLinerList(SequnceLinerList& list, int value)
{
for (unsigned int i = 0; i < list.length;++i)
{
if (value == list.elem[i])
{
return i;
}
}
return -1;
}
/* 排序(冒泡) */
void sortSequeceLinerList(SequnceLinerList& list)
{
unsigned int i = 0;
unsigned int j = 0;
for (; i < list.length-1;++i)
{
for (j = i + 1; j < list.length; ++j)
{
if (list.elem[i]>list.elem[j])
{
LIST_TYPE tmp = list.elem[i];
list.elem[i] = list.elem[j];
list.elem[j] = tmp;
}
}
}
}
/* 销毁 */
void destroySequenceLincerList(SequnceLinerList& list)
{
delete list.elem;
list.elem = NULL;
}
#endif
4.线性表的链式实现:通过p=p.next查找具体位置的链表。特点:插入、删除快O(n)。
#include <stdio.h>
#include <stdio.h>
#ifndef LINK_LINER_LIST_H
#define LINK_lINER_LIST_H
#define LINK_LIST_TYPE int
/*
线性表的链表实现
*/
typedef struct strLinkLinerList
{
LINK_LIST_TYPE data;
struct strLinkLinerList* pNext;
}LinkLinerList;
LinkLinerList* head = NULL;
/* 链表初始化 */
Status initLinkLinerList()
{
if (NULL == head)
{
head = (LinkLinerList*)malloc(sizeof(LinkLinerList));
if (!head)
{
return OVERFLOW;
}
head->pNext = NULL;
return OK;
}
else
{
return FAILED;
}
}
/* 添加元素 */
Status insertLinkLinerList(LINK_LIST_TYPE value)
{
if (NULL == head)
{
return FAILED;
}
LinkLinerList* p = head;
while (p->pNext)
{
p = p->pNext;
}
LinkLinerList* tmp = (LinkLinerList*)malloc(sizeof(LinkLinerList));
if (!tmp)
{
return OVERFLOW;
}
tmp->data = value;
tmp->pNext = NULL;
p->pNext = tmp;
return OK;
}
/*打印*/
void printLinkLinerList()
{
printf("Begin print...\n");
if (NULL == head || NULL == head->pNext)
{
printf("List is null.");
}
LinkLinerList* p = head->pNext;
while (p->pNext)
{
printf("%d,",p->data);
p = p->pNext;
}
printf("%d\n", p->data);
printf("End print.\n");
}
/* 排序 */
Status sortLinkLinerList()
{
if (NULL == head || NULL == head->pNext)
{
return FAILED;
}
LinkLinerList* p = head->pNext;
LinkLinerList* q = p->pNext;
while (p)
{
q = p->pNext;
while (q)
{
if (p->data > q->data)
{
LINK_LIST_TYPE tmp = p->data;
p->data = q->data;
q->data = tmp;
}
q = q->pNext;
}
p = p->pNext;
}
return OK;
}
/* 长度 */
unsigned int lengthLinkLinerList()
{
if (head == NULL || head->pNext == NULL)
{
return 0;
}
LinkLinerList* p = head->pNext;
unsigned int length = 0;
while (p)
{
p = p->pNext;
length++;
}
return length;
}
/* 删除 */
Status deleteLinkLinerList(unsigned int position)
{
if (position > lengthLinkLinerList() || position < 1)
{
return FAILED;
}
else
{
LinkLinerList* p = head;
LinkLinerList* q = p->pNext;
if (NULL == p || NULL == q)
{
return FAILED;
}
while (--position)
{
p = p->pNext;
q = p->pNext;
}
p->pNext = q->pNext;
free(q);
return OK;
}
}
/* 销毁 */
void destroyLinkLinerList()
{
if (NULL == head)
{
return;
}
else
{
LinkLinerList* p = head->pNext;
LinkLinerList* q = p;
while (p)
{
p = p->pNext;
free(q);
q = p;
}
}
}
#endif
上面实现的是线性表的链式结构。此外,线性表通过链表还可以实现:
- 循环列表:表最后的一个节点的指针指向表头
- 双列表:指针可以指向下一个元素或者上一个元素
5.线性表的用途:
- 一元多项式的表示和相加
第3章 栈和队列
1.栈和队列是特殊的线性表。
2.栈是先进后出的线性表,其表示:typedef struct{Type *base;Type *top; int stacksize;}SqStack
;其中base始终指向栈底,top随元素的添加一直指向栈顶。可以有顺序实现和链表实现。
3.栈的应用举例:
- 数制转换
- 括号匹配
- 行编辑器帮助处理错误输入
- 迷宫求解
- 表达式求解
- 实现递归
3.队列是先进先出的线性表。可以由链表实现和顺序实现。
第四章 串
1.串是由零到多个字符组成的字符序列。
2.串的模式匹配算法(查找子串):
- 找到主串中第一个等于子串首字符的位置,开始逐步遍历子串和主串:时间复杂度O(n*(m-n+1)),其中m,n是主串、子串长度
- kmp算法:关键是部分匹配值的计算("部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置)。时间复杂度O(n+m)。KMP算法仅当子串与主串之间存在很多"部分匹配"的情况下才快的多。详见
http://kb.cnblogs.com/page/176818/
。
第五章 数组和广义表
1.广义表(Lists,又称列表)是一种非线性的数据结构,是线性表的一种推广。即广义表中放松对表元素的原子限制,容许它们具有其自身结构。
第六章 树和二叉树
1.树的相关概念:
- 度:节点的子树数目称之为节点的度。度为0的节点称之为叶子。
- 深度:树中结点的最大层次。
2.二叉树子节点至多有两个,且有左右之分。二叉树有五种基本形态:空、根、右子树为空、左右子树非空、左子树为空。二叉树的性质:
- 第i层有2^(i-1)个节点
- 深度为k的二叉树至多2^k-1个节点
- 终端节点数为n,度为2的节点数为m,则n=m+1
- 具有n个结点的完全二叉树的深度是(log2N)+1
3.二叉树分类:
- 满二叉树:深度为k且有2^k-1个结点的二叉树
- 完全二叉树:深度为k,有n个结点的二叉树,每个结点的编号与深度为k的满二叉树编号一一对应
4.二叉树的存储结构:
- 顺序结构:按照完全二叉树的编号将其存放在一位数组中标有i-1的分量中。
- 链表结构:
typedef struct BitNode{ElemType data; struct BitNode *lchild,*rchild}BitNode;
5.二叉树的遍历:
- 先序(根)遍历
- 中序(根)遍历
- 后序(根)遍历
先根和后根反映的是双亲与孩子节点的关系,中根反映的是兄弟节点之间的左右次序。对于表达式而言,这三种遍历分别前缀表达式(波兰表达式)、中缀表达式和后缀表达式(逆波兰表达式)。
6.二叉树的确定:
- 已知先根和中根遍历可以建立唯一二叉树
- 已知后根和中根遍历可以建立唯一二叉树
- 已知先根和后根遍历不可以建立唯一二叉树
7.二叉树的遍历可以使用递归实现,其非递归中根遍历的算法是(使用栈操作):
[图片上传失败...(image-5f478f-1524397579659)]
8.二叉树从根开始分层从左至右遍历算法是(使用队列操作):
9.线索二叉树:指向前驱或者后继的节点的链成为线索。线索二叉树使用的节点结构为:data、left、right、ltag、rtag。其中ltag(rtag)=0表示子树,=1表示前驱(后继)节点。
10.赫夫曼树又称最优树,是一类带权路径长度(子叶到根的路径个数)最短的树。赫夫曼树的构造算法:赫夫曼算法:
- (1)以权值分别为W1,W2...Wn的n各结点,构成n棵二叉树T1,T2,...Tn并组成森林F={T1,T2,...Tn},其中每棵二叉树 Ti仅有一个权值为 Wi的根结点;
- (2)在F中选取两棵根结点权值最小的树作为左右子树构造一棵新二叉树,并且置新二叉树根结点权值为左右子树上根结点的权值之和(根结点的权值=左右孩子权值之和,叶结点的权值= Wi)
- (3)从F中删除这两棵二叉树,同时将新二叉树加入到F中;
- (4)重复(2)、(3)直到F中只含一棵二叉树为止,这棵二叉树就是Huffman树。
11.赫夫曼树的应用:
- 不同分数段评定优良中差,可以按照分数出现比例为权值构造赫夫曼数,优化程序判断次序
- 赫夫曼编码
第七章 图
1.图的遍历算法:
- 深度优先算法
- 广度优先算法
第八章 动态管理内存
略
第九章 查找
1.查找算法(详见https://www.cnblogs.com/yw09041432/p/5908444.html
)。平均查找长度(Average Search Length,ASL):需和指定key进行比较的关键字的个数的期望值,称为查找算法在查找成功时的平均查找长度。对于含有n个数据元素的查找表,查找成功的平均查找长度为:ASL = Pi*Ci的和。Pi:查找表中第i个数据元素的概率。Ci:找到第i个数据元素时已经比较过的次数。
- (1)顺序表查找:顺序查找:
- 要求:存储结构为顺序存储或者链表存储
- 思想:顺序依次查找
- ASL:(1/n)*(1+2+...+n)=(n+1)/2
- 时间复杂度:O(n)
- (2)二分查找、折半查找:
- 要求:有序顺序存储列表
- 思想:先将查找值与中间值比较,查找不成功折半查找区间
- ASL:log2(n+1)
- 时间复杂度:O(log2n)
- (3)插值查找:
- 要求:有序顺序存储列表
- 思想:基于二分查找算法,将查找点的选择改进为自适应选择(由mid=low+1/2(high-low)改为mid=low+(key-a[low])/(a[high]-a[low])(high-low),),可以提高查找效率。
- 时间复杂度:O(log2n)
- (4)斐波那契额查找:
- 要求:有序顺序存储列表
- 思想:依然是对查找点的优化,采用Fibonacci数组,找到对于当前长度的有序表的黄金分割点,作为每次的中间值。
- 时间复杂度:O(log2n)。平均性能,菲波那切查找优于折半查找,最坏情况低于折半查找。
- (5)二叉查找树:
- 要求:需要首先二叉查找树(左子树<根<右子树)
- 思想:先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围
- 时间复杂度:O(log2n),最坏O(n),原因是没有保持平衡,形成单支树
- (6)平衡二叉树AVL:
- 要求:二叉查找树的基础上,需要进行树的平衡
- 时间复杂度:一直是O(log2n)
- 此外,在此基础上还有2-3树,2-3-4树,B+树,红黑树等。
2.上述算法中:
- 顺序表查找:(1)
- 优点:简单
- 缺点:效率低O(n)
- 有序表查找:(2)/(3)/(4)
- 优点:时间复杂度低O(log2n)
- 缺点:有序表的插入和删除性能是比较差的
第十章 第十一章 排序
1.排序算法的稳定性:经过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj之前,而在排序后的序列中,ri仍在rj之前,则称这种排序算法是稳定的;否则称为不稳定的。例如冒泡排序是稳定的,但是如果交换条件改为a[j].key>=a[j+1].key,就是不稳定的。
2.排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
3.常见的8种内部排序算法有(详见https://www.cnblogs.com/RainyBear/p/5258483.html
):
- 插入排序:将第一个元素看成是有序的序列,之后的元素看成是未排序的序列,然后遍历未排序的序列,将其插入到有序序列中
- 希尔排序:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序
- 选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
- 冒泡排序:比较相邻元素,如果前面的大就交换
- 归并排序
- 快速排序
- 堆排序
- 基数排序
第十二章 文件
略