写在前面
你们好,我是小庄。很高兴能和你们一起学习数据结构。如果您对Java感兴趣的话可关注我的动态.
写博文是一种习惯,在这过程中能够梳理知识和巩固知识点。
1、表
思路:
- 定义表结构
- 初始化空表
- 对表进行操作
顺序表
typedef int ListItem;//设置表元素的类型
/**表的基本结构**/
typedef struct list{
int n;//表的长度
int curr;//当前位置
int maxsize;//数组的上界
ListItem *table;//表数组
}List;
//-------------------表结构定义end------------------------
/**初始化表结构**/
List ListInit(int size)//size表示表结构长度
{
List L=(List)malloc(sizeof *L);//给结构体分配一个空间
L->table=(ListItem *)malloc(size*sizeof(ListItem));//给表数组分配长度为size,size是动态调用设置的值
L->maxsize=size;//设置数组的上界(数组总长度),以便插入表判断数组是否是满的(判断是否会越界)。
L->n=0;//表是空表,所以长度为0
/**这里不对curr进行操作**/
return L;//返回空表
}
//--------------------初始化表end----------------------
/**插入表**/
void insert(int k,ListItem x,List L)//k表示插入位置,x表示插入元素,L表示插入的表
{
//先判断插入的位置是否合法,要保证表的连续性
if(k<0||k>L->n) return ;//如果长度小于0或者大于表的长度,直接返回
for(int i=L->n-1;i>=k;i--){
//最后一个元素开始,把前面的值赋值给后面(元素移动方向从前往后)这里的i+1表示最后一个元素下标
L->table[i+1]=L->table[i];
L->table[k]=x;//在位置k插入元素
L->n++;//表长度+1
}
}
//----------------------插入表元素end---------------------
/**删除表**/
//这里返回删除的元素
ListItem ListDelete(int k,List L)//k表示删除位置,L表示删除的表
{
if(k<0||k>L->n)return 0;//返回值为0,表示删除失败,删除位置不合法
ListItem x=L->table[k-1];
for(int i=k;i<L->n;i++){
//删除的元素开始,把后面的值赋值给前面(元素移动方向从后往前)
L->table[i-1]=L->table[i];
L->n--;//表长度-1
return x;//返回删除的元素
}
}
//---------------------删除表元素end----------------------
//由于顺序表修改和查询操作都比较简单,这里就不进行介绍了
链表
//ListItem就是int类型
typedef struct node *link;//表结点指针类型
typedef struct node{
ListItem element;//表元素
link next;//这里等于node *next;指向下一个结点的指针
}Node;
//创建一个新结点
link NewNode(){
return (link)malloc(sizeof(Node));
}
//表结构
typedef struct llist{
link first;//表首指针
link curr;//当前结点指针
link last;//表尾指针
}Llist;
//-------------------表结构end-----------------------
/**初始化链表**/
Llist ListInit(){
Llist L=(Llist)malloc(sizeof *L);//动态生成空间长度的链表
L->first=0;//链表表首置为0;
return L;//返回空表
}
//----------------初始化链表end----------------------
/**插入**/
void Insert(int k,ListItem x,Llist L)//k为位置,x为元素,L为链表
{
if(k<0) return ;//位置不合法直接返回
link p=L->first;
for(int i=1;i<k&&p;i++)//p为0时退出循环
{
//遍历链表
p=p->next;
}
//创建一个新结点存放新数据
link y=NewNode();
y->element=x;//把数据存放进去
//判断链表是否为空
if(k){//不是空表
y->next=p->next;//把新结点的下一个结点连接原链表的下一个结点
p->next=y;//把原链表的下一个结点连接新结点
}
else{
y->next=L->first;//把新结点的下一个结点连接表首
L->first=y;//把新结点做为表首
}
}
2、栈
栈的特点: 先进后出,只能在栈顶进行插入和删除操作
思路
- 定义栈结构
- 初始化栈空间
- 对栈顶操作前,判断栈是否溢出(上溢或下溢)
数组实现栈
//StackItem设置为int类型
typedef int StackItem;//指定栈元素的类型
typedef struct astack *Stack;//指定栈指针类型
typedef struct astack{
int top;//栈顶
int maxtop;//栈空间上限,用于判断是否上溢
StackItem *data;//存储栈元素的数组,指针可表示数组
}Astack;
//---------------------栈结构end-----------------------
/**初始化栈**/
Stack StackInit(int size)//size表示空间长度
{
Stack S=(Stack)malloc(sizeof *S);//分配栈空间
S->data=(StackItem)malloc(size*sizeof(StackItem));//分配数组空间
S->top=-1;//设置栈顶的初始值
S->maxtop=size;//设置栈空间上限
return S;
}
//-----------------初始化栈end-------------------------
/**入栈**/
void Push(StackItem x,Stack s)//x为入栈的元素,s为栈
{
//先判断栈是否已经满了,即栈顶达到上限
if(S->top=S->maxtop){
return;//直接返回
}else{//没满,栈顶往上移动一个单位,数据入栈
S->top++;
S->data[S->top]=x;//入栈到新的空间
}
}
//----------------入栈end-----------------------
/**出栈**/
StackItem Pop(Stack S){
//判断栈是否下溢
if(S->top<0){//已经是空栈了,不能再出栈了
return 0;
}else{
//数据先出栈,栈顶往下移动一个单位
StackItem x=S->data[S->top];
S->top--;//栈顶往下移动
return x;//返回栈顶元素
}
}
//------------------出栈end----------------------
/**释放栈空间**/
void StackFree(Stack S){
free(S->data);
free(S);
}
//----------------释放栈空间end---------------------
指针实现栈
特点:对链首进行
typedef int StackItem;//栈元素类型
typedef struct snode *slink;//栈结点指针
//栈链表结构
typedef struct snode{
StackItem element;//栈元素
slink next;//指向下一个指针
}StackNode;
//创建新结点
StackNode NewNode(){
StackNode node=(StackNode)malloc(sizeof(StackNode));//分配结点空间
return node;
}
//栈结构
typedef struct lstack *Stack;//栈指针元素
typedef struct lstack{
slink top;//设置栈顶
}Lstack;
//-----------------栈结构end----------------------------
/**初始化栈**/
Stack StackInit(){
Stack S=(Stack)malloc(sizeof*S);
S->top=0;
return S;
}
//-----------------初始化栈end--------------------------
/**入栈**/
void Push(StackItem x,Stack S){
slink p=NewNode();//分配一个结点空间
p->element=x;//将数据存入结点
p->next=S->top;//把新结点做为链首
S->top=p;//将栈顶指向链首
}
//-------------------入栈end---------------------------
/**出栈**/
StackItem Pop(Stack S){
if(S->top==0){
return 0;
}else{
StackItem x=S->top->element;//获取当前栈顶的元素,做为出栈的元素
/**将栈顶指向下一个元素**/
slink p=S->top;
S->top=p->next;
free(p);//释放出栈的空间
return x;//返回出栈元素
}
}
//-------------------出栈end--------------------------
3、队列
队列的特点:先进先出
思路:
- 定义队列结构,这里要设置队首和队尾的指针
- 初始化空间
- 队首进行出队,队尾进行入队
指针实现队列
typedef int QItem;//队列元素类型
typedef struct qnode *qlink;//队列结点指针
typedef struct qnode{
QItem element;//队列元素
qlink next;//下一个元素
}Qnode;
//创建新结点
Qnode NewQnode(){
qlink q=(Qnode)malloc(sizeof(Qnode));//分配结点空间
return q;
}
//队列结构
typedef struct lqueue *Queue;
typedef struct lqueue{
qlink frist;//队首指针
qlink end;//队尾指针
}Lqueue;
//-------------------队列结构end----------------------
/**初始化队列**/
Queue QueueInit(){
Queue Q=(Queue)malloc(sizeof *Q);//分配队列空间
//将队首队尾归为0;
Q->frist=Q->end=0;
return Q;
}
//---------------初始化队列end-----------------------
/**入队**/
void EnterQueue(QItem x,Queue Q){//x为元素,Q为队列
qlink q=NewQnode();//分配结点空间
q->element=x;//将数据添加到结点
q->next=0;//先将下一结点置为空
if(Q->frist){//队列不为空的条件
Q->end->next=q;//将原队尾往后面插入新的结点
Q->end=q;//重新将队尾指向新结点
}else{//队列为空,直接指向队尾的是队首
Q->frist=q;//队首指针指向新结点
Q->end=q;//队尾指针指向新结点
}
}
//-----------------入队end--------------------------
/**出队**/
QItem DeleteQueue(Queue Q){
//先判断队列是否为空
if(Q->frist==0){return 0;//直接返回0}
QItem x=Q->frist->element;//获取当前队首的元素
qlink p=Q->frist;//存放当前队首的结点,用于释放空间
Q->frist=Q->frist->next;//将队首指向下一个结点
free(p);
return x;//返回出队的元素;
}
//-----------------出队end--------------------------
循环数组实现队列
特点:运用到模运算
typedef int QItem;//队列元素
typedef struct aqueue *Queue;//队列指针类型
typedef struct aqueue{
int maxsize;//循环数组的大小
int frist;//队首游标
int end;//队尾游标
QItem *table;//循环数组
}Aqueue;
//----------------队列结构end--------------------
/**初始化队列**/
Queue QueInit(QItem size){
Queue q=(Queue)malloc(sizeof(Queue));//分配队列空间
q->table=(QItem)malloc(size*sizeof(QItem));//分配数组空间
q->maxsize=size;//设置循环数组大小
q->frist=q->end=0;//设置队首队尾初始值
return q;
}
//--------------初始化队列end----------------------
/**入队**/
void EnterQueue(QItem x,Queue Q){
//判断队列是否满,设置空出一个空间
QItem b=((Q->end+1)%Q->maxsize==Q->frist)?1:0;//满时模为0;否则为1
if(b){return ;}//满的时候直接返回
else{
Q->end=(Q->end+1)%Q->maxsize;//重新设置队尾
Q->table[Q->end]=x;//将数据入队
}
}
//-----------------入队end-------------------------
/**出队**/
QItem deleteQueue(Queue Q){
//判断队列是否为空
if(Q->frist==Q->end)return 0;
QItem x=Q->table[Q->frist];//保存出队的数据
Q->frist=(Q->frist+1)%Q->maxsize;//重新设置队首,往后移动一个单位
return x;//返回出队数据
}
//-----------------出队end---------------------------
4、二叉树
特点: 用递归的思想
指针建立二叉树
typedef int TreeItem;//树元素类型
typedef struct tnode *tlink;
typedef struct tnode{//树结点
TreeItem element;//结点元素
tlink left;//左子树
tlink right;//右子树
}Tnode;
//创建新结点
Tnode Newtnode(){
Tnode t=(Tnode)malloc(sizeof(Tnode));//分配结点
return t;
}
typedef struct tree{//树结构
tlink root;//树根
}Tree;
//----------------树结构end-----------------------------
/**初始化树**/
Tree TreeInit(){
Tree t=(Tree)malloc(sizeof *t);//分配树空间
t->root=0;//设置默认根结点的值为0
return t;
}
//-----------------初始化end----------------------------
/**建立一个二叉树**/
void MakeTree(TreeItem x,Tree Left,Tree Right,Tree T)//x为结点数据,Left为左子树,Right为右子树,T为新树
{
T->root=Newtnode();//分配一个结点空间
T->root->element=x;//把数据放到结点中
T->root->left=Left->root;//左子树连接原子树的根结点
T->root->right=Right->root;//右子树连接原子树的根结点
Left->root=Right->root=0;//设置新树的左右子树的值为空
}
//------------------建树end----------------------------
二叉树遍历
遍历特点:根据根结点的位置不同分为前序、中序、后序遍历
typedef int TreeItem;//树元素类型
typedef struct bnode *blink;
typedef struct bnode{
TreeItem element;//结点元素
tlink left;//左子树
tlink right;//右子树
}Bnode;
/**前序遍历**/
void PreOrder(blink t){
if(t){//树非空
printf("%d",t->element);//输出结点的元素值,这里是int类型
PreOrder(t->left);
PreOrder(t->right);
}
}
//-----------------前序遍历end------------------------
/**中序遍历**/
void PreOrder(blink t){
if(t){//树非空
PreOrder(t->left);
printf("%d",t->element);//输出结点的元素值,这里是int类型
PreOrder(t->right);
}
}
//----------------中序遍历end-------------------------
/**后序遍历**/
void PreOrder(blink t){
if(t){//树非空
PreOrder(t->left);
PreOrder(t->right);
printf("%d",t->element);//输出结点的元素值,这里是int类型
}
}
//-----------------后序遍历end-------------------------
二叉搜索树
特点:
- 设 t 是二叉树的一个节点,如果 x 是左子树的一个节点,则t.key>=x.key。如果y是右子树的一个节点,则t.key<=y.key。key表示节点的值
- 中序排序是有序的
- 相比普通二叉树的树结构多一个父结点
简单来讲特点1: 左子树比父节点小,右子树比父节点大。
typedef int TreeItem;//二叉搜索树元素类型
typedef struct snode *slink;//二叉搜索树结点的指针类型
//二叉搜索树的结点
typedef struct snode{
TreeItem element;//二叉树结点元素
slink left;//左子树
slink right;//右子树
slink parent;//父节点指针
}Snode;
//创建一个新结点
Snode Newsnode(){
Snode s=(slink)malloc(sizeof(Snode));//分配一个结点空间
return s;//返回新结点
}
typedef struct tree{//树结构
slink root;//树根
}Tree;
/**初始化树**/
Tree TreeInit(){
Tree t=(Tree)malloc(sizeof *t);//分配树空间
t->root=0;//设置默认根结点的值为0
t->left=0;
t->right=0;
t->parent=0;
return t;
}
//-----------------初始化end----------------------------
/**二叉搜索树插入操作**/
int SInsert(TreeItem x,slink t){
if(t->root==0){//空树
t->root->element=x;//把数据直接插入树根结点
return 1;//返回状态
}else if(x<t->root->element){//数据比根结点的值小,插入左边
if(t->left!=0){//如果不是叶子,则进行递归
Sinsert(x,t->left);//递归操作
}
Snode r=NewSnode();//创建新结点
r->element=x;//将数据放入新结点
t->left=r;//往左边插入结点
return 1;//返回状态
}
else if(x>t->root->element){//数据比根结点的值大,插入右边
if(t->right!=0){//如果不是叶子,则进行递归
Sinsert(x,t->right);//递归操作
}
Snode r=NewSnode();//创建新结点
r->element=x;//将数据放入新结点
t->right=r;//往右边插入结点
return 1;//返回状态
}else{
return 0;//返回错误状态
}
}
//-----------------插入操作end----------------------
单独讲二叉搜索树删除
分三种情况
- 删除结点是叶子结点,直接删除
- 删除结点只有一个孩子结点:a、根结点 b、子结点
- 删除结点有两个孩子结点:左子树最大值的结点与该结点替换,并删除替换掉的结点,把替换掉的结点的子结点连接父结点
注:树的构造用的是上面的代码
slink DeleteTreeNode(TreeItem x,slink node){
//情况一
if(node.left==node.right==0){//没有左右结点,说明是叶子结点或者是根结点
if(node.parent==0){//说明没有是根结点
node=0;
}else if(x > node.element){//是右孩子
node.parent.right=0;
}
else{
node.parent.left=0;
}
}
//情况二
else if(node.left!=0&&node.right==0){
node=node.left;
}else if(node.left==0&&node.right!=0){
node=node.right;
}
//情况三
else{
slink max;//用于存放删除结点的左子树最大的结点
while(node.left){//遍历最右的叶子结点
max=node.left;//存左子树最大结点
node.left=node.right;
}
node.element=node.left.element//替换结点的值
node=node.left;//把删除的结点指向左子树最大的叶子结点
if(node.right!=0){//变为情况二,只有右子树
node=node.right;
}else{//没有子树,情况一,并且自己是左子树
node.parent.left=0;
}
}
}
//------------------二叉树删除操作end-----------------------
哈夫曼树
(1)、最优二叉树:
a、权值越大的叶子离根越近
b、具有相同带权结点的哈夫曼树不唯一
c、包含n个叶子结点的哈夫曼树中共有2n-1个结点
d、包含n棵树的森林要经过n-1次合并才能形成哈夫曼树,共产生n-1个新结点。
e、满二叉树不一定是哈夫曼树
(2)、如何构造一个哈夫曼树?
a、在 n 个权值中选出两个最小的权值,对应的两个结点组成一个新的二叉树,且新二叉树的根结点的权值为左右孩子权值的和;
b、在原有的 n 个权值中删除那两个最小的权值,同时将新的权值加入到 n–2 个权值的行列中,以此类推;
c、重复 a 和 b (使用递归),直到所有的结点构建成了一棵二叉树为止,这棵树就是哈夫曼树。
(3)、结构元素:weight(结点权重);parent,left,right(父结点、左孩子,右孩子在数组中的位置下标)
(4)、哈夫曼编码:
在建立哈夫曼树后,在左孩子结点设置标为0,右孩子结点设置标为1。用哈夫曼树设计总长最短的二进制前缀编码。