数据结构是指相互之间存在着一种或多中关系的数据元素的结合及其操作,这种数据元素之间的关系称为结构。分为:逻辑结构,存储结构
线性表
n个具有相同特性的数据元素的有限序列
顺序存储结构
在内存中开辟一片连续的存储空间,用一组连续的存储单元依次存放线性表的数据元素,这种存储方式叫做线性表的顺序存储结构,简称顺序表。
优点:1.查找方便
2.查找容易
缺点:1.大小不能事先确定
2.增加数据不方便 需要下移数据
3.删除中间数据不方便 需要上移数据
链表
用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),数据元素之间的逻辑关系借助指示元素存储位置的指针来表示,
这种存储方式叫做线性表的链式存储结构,简称链表。或单链表
结点-> 元素-> 一条数据
优点:1.大小不用事先确定
2.增加方便
3.删除方便
4.修改方便
5.查找方便
typedef struct student{
}stu;
定义结构体别名stu 就可以这样定义结构体变量 stu s;
无头单链表存储的一个例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student
{
int id;
char name[20];
int math;
struct student *next;
}stu;
stu* create()
{
stu *head=NULL;
stu *p=NULL;
stu *new=NULL;
int iid=0;
char iname[20]="";
int imath=0;
head=malloc(sizeof(stu));
if(head==NULL)
{
return NULL;
}
else
{
iid++;
printf("输入姓名:");
scanf("%s",iname);
printf("输入成绩:");
scanf("%d",&imath);
head->id=iid;
strcpy(head->name,iname);
head->math=imath;
head->next=NULL;
p=head;
while(1)
{
iid++;
printf("输入姓名,输0退出:");
scanf("%s",iname);
if(iname[0]=='0')
{
break;
}
printf("输入成绩:");
scanf("%d",&imath);
new=malloc(sizeof(stu));
new->id=iid;
strcpy(new->name,iname);
new->math=imath;
new->next=NULL;
p->next=new;
p=new;
}
}
return head;
}
int select1(stu *head,char *sname) //查找函数,输入指定的名字进行查找
{
stu *p=head;
while(p!=NULL)
{
if(!(strcmp(p->name,sname)))
{
printf("id=%d\n",p->id);
printf("name=%s\n",p->name);
printf("math=%d\n",p->math);
return 1;
}
p=p->next;
}
return 0;
}
int insert1(stu *head,char *sname,int iid,char *iname,int imath) //新增函数,输入id,name,math,和指定插入的元素后面
{
stu *p=NULL;
stu *new=NULL;
p=head;
while(p!=NULL)
{
if(!(strcmp(p->name,sname)))
{
new=malloc(sizeof(stu));
new->id=iid;
strcpy(new->name,iname);
new->math=imath;
new->next=p->next;
p->next=new;
return 1;
}
p=p->next;
}
return 0;
}
int delete1(stu *head,char *dname) //删除指定元素
{
stu *p=NULL;
stu *back=NULL;
p=head;
while(1)
{
back=p;
p=p->next;
if(!(strcmp(p->name,dname)))
{
back->next=p->next;
free(p);
return 1;
}
if(p!=NULL && p->next==NULL)
{
break;
}
}
return 0;
}
void destroy1(stu *head) //释放内存
{
stu *p=NULL;
while(head!=NULL)
{
p=head;
head=head->next;
printf("p->name=%s\n",p->name);
free(p);
}
}
void print(stu *head)
{
stu *p=head;
while(p!=NULL)
{
printf("id=%d\n",p->id);
printf("name=%s\n",p->name);
printf("math=%d\n",p->math);
p=p->next;
}
}
int main()
{
stu *h=create();
print(h);
return 0;
}
有头单链表的一个例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student
{
int id;
char name[20];
int math;
struct student *next;
}stu;
stu* create()
{
stu *head=NULL;
stu *p=NULL;
stu *new=NULL;
int iid=0;
char iname[20]="";
int imath=0;
head=malloc(sizeof(stu));
if(head==NULL)
{
return NULL;
}
else
{
head->id=0;
strcpy(head->name,"");
head->math=0;
head->next=NULL;
p=head;
while(1)
{
iid++;
printf("输入姓名,输0退出:");
scanf("%s",iname);
if(iname[0]=='0')
{
break;
}
printf("输入成绩:");
scanf("%d",&imath);
new=malloc(sizeof(stu));
new->id=iid;
strcpy(new->name,iname);
new->math=imath;
new->next=NULL;
p->next=new;
p=new;
}
}
return head;
}
void print(stu *head)
{
stu *p=head->next;
while(p!=NULL)
{
printf("id=%d\n",p->id);
printf("name=%s\n",p->name);
printf("math=%d\n",p->math);
p=p->next;
}
}
int select1(stu *head,char *sname)
{
stu *p=head->next;
while(p!=NULL)
{
if(!(strcmp(p->name,sname)))
{
printf("id=%d\n",p->id);
printf("name=%s\n",p->name);
printf("math=%d\n",p->math);
return 1;
}
p=p->next;
}
return 0;
}
int insert1(stu *head,char *sname,int iid,char *iname,int imath)
{
stu *p=NULL;
stu *new=NULL;
p=head->next;
while(p!=NULL)
{
if(!(strcmp(p->name,sname)))
{
new=malloc(sizeof(stu));
new->id=iid;
strcpy(new->name,iname);
new->math=imath;
new->next=p->next;
p->next=new;
return 1;
}
p=p->next;
}
return 0;
}
int delete1(stu *head,char *dname)
{
stu *p=NULL;
stu *back=NULL;
p=head;
while(1)
{
back=p;
p=p->next;
if(!(strcmp(p->name,dname)))
{
back->next=p->next;
free(p);
return 1;
}
if(p!=NULL && p->next==NULL)
{
break;
}
}
return 0;
}
void destroy1(stu *head)
{
stu *p=NULL;
while(head!=NULL)
{
p=head;
head=head->next;
printf("p->name=%s\n",p->name);
free(p);
}
}
int main()
{
stu *h=create();
destroy1(h);
return 0;
}
环链
当只有一个元素的时候也得是环链
有头单环链的一个例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student
{
int id;
char name[20];
int math;
struct student *next;
}stu;
stu* create()
{
stu *head=NULL;
stu *p=NULL;
stu *new=NULL;
int iid=0;
char iname[20]="";
int imath=0;
head=malloc(sizeof(stu));
if(head==NULL)
{
return NULL;
}
else
{
head->id=0;
strcpy(head->name,"");
head->math=0;
head->next=head;
p=head;
while(1)
{
iid++;
printf("输入姓名,输0退出:");
scanf("%s",iname);
if(iname[0]=='0')
{
break;
}
printf("输入成绩:");
scanf("%d",&imath);
new=malloc(sizeof(stu));
new->id=iid;
strcpy(new->name,iname);
new->math=imath;
new->next=head;
p->next=new;
p=new;
}
}
return head;
}
void print(stu *head)
{
stu *p=head->next;
while(p!=head)
{
printf("id=%d\n",p->id);
printf("name=%s\n",p->name);
printf("math=%d\n",p->math);
p=p->next;
}
}
int select1(stu *head,char *sname)
{
stu *p=head->next;
while(p!=head)
{
if(!(strcmp(p->name,sname)))
{
printf("id=%d\n",p->id);
printf("name=%s\n",p->name);
printf("math=%d\n",p->math);
return 1;
}
p=p->next;
}
return 0;
}
int insert1(stu *head,char *sname,int iid,char *iname,int imath)
{
stu *p=NULL;
stu *new=NULL;
p=head->next;
while(p!=head)
{
if(!(strcmp(p->name,sname)))
{
new=malloc(sizeof(stu));
new->id=iid;
strcpy(new->name,iname);
new->math=imath;
new->next=p->next;
p->next=new;
return 1;
}
p=p->next;
}
return 0;
}
int delete1(stu *head,char *dname)
{
stu *p=NULL;
stu *back=NULL;
p=head;
while(1)
{
back=p;
p=p->next;
if(!(strcmp(p->name,dname)))
{
back->next=p->next;
free(p);
return 1;
}
if(p!=head && p->next==head)
{
break;
}
}
return 0;
}
void destroy1(stu *head)
{
stu *p=NULL;
stu *ph=head->next;
while(ph!=head)
{
p=ph;
ph=ph->next;
printf("p->name=%s\n",p->name);
free(p);
}
free(head);
}
int main()
{
stu *h=create();
destroy1(h);
return 0;
}
有头双向链表
恒等式
p->prior->next = p = p-next->prior
typedef void aa(void *); 这样写就可以用函数来定义指针*p
aa *p; 函数类型的指针
int (*p)(void*) 可以这样写
可变长结构体
struct a{
int a,
double b,
int data[0];
};
struct stu{
int id;
char name[20];
int math;
};
int main(){
struct a *p=NULL;
p=malloc(sizeof(struct a)+sizeof(int)*3);
free(p);
return 0;
}
data就会获得3个int大小的空间
可变长数据类型必须放在结构体定义变量的最后面
如果这样写 char data[0];
就会获得3个char 大小的空间
struct stu s={101,"zhangsan",100};
struct a *p=malloc(sizeof(struct a)+sizeof(struct stu));
p->a=11;
p->b=22;
memcpy(p->data,&s,sizeof(struct stu)); //通过memcpy就可以把s结构体变量存放的内容拷贝到data中
memmove(p,p1,sizeof()); //把p1指向的地址覆盖p指向的地址内容,大小为后面指定的大小
读出拷贝的数据
void printf(void *data){
struct stu *p=data;
printf("p->id=%d",p->id);
printf("p->name=%s",p->name);
printf("p->math=%d",p->math);
}
snprintf(datap->name,NAMESIZE,"stu%d",i); //将"stu%d"(其中%d,是i的值),给datap->name 最大为NAMESIZE大小
栈和队列
栈是限制仅在表的一端进行插入和删除操作的线性表
允许插入和删除的一端是栈顶(top),反之为栈底(bottom)
不含元素的栈成为空栈
后进先出原则LILO
顺序栈:顺序表来存储元素,栈指针top,指向实际栈顶后的空位置,用一维数组来表示栈
链栈:用链表来存储元素,链表尾部结点为栈底,头部结点为栈顶
队列
只能在表的一端进行插入,在表的另一端删除的线性表
队尾(rear): 允许插入的一端
队头(front): 允许删除的一端
先进先出FIFO
顺序队:用一维数组进行存储,有一个指示队头和队尾的下标指针
链式队列:同时带有头指针和尾指针单链表,头指向头结点,尾指向尾结点
树
树(Tree)是n(n≥0)个结点的有限集,通常用T表示。当n=0时,称为空树,记作Φ。
否则,在任一非空树中:
1)有且仅有一个称之为根(Root)的结点。
2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,……Tm,其中每一个集合本身又是一棵树,称为根的子树(subtree)。
结点(node):表示树中的元素,包括数据项及若干指向其子树的分支。
结点的度(degree):是指结点拥有的子树个数。
树的度:一棵树中最大的结点度数。
分支结点(branch):度大于0的结点称为分支结点或者非终端结点。
叶子结点(leaf):度为0的结点称为叶子结点或者终端结点。
孩子结点(child):结点子树的根称为该结点的孩子。
双亲结点(parents):孩子结点的上层结点叫该结点的双亲结点。
兄弟结点(sibling):同一双亲的孩子互称为兄弟结点。
堂兄弟结点(cousin):双亲在同一层上的结点互称为堂兄弟结点。
祖先结点(ancestor):从根到该结点的所经路径上的所有结点均称为祖先结点。
子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙结点。
结点的层次(level):树具有层次结构。从根结点算起,根为第一层,它的孩子为第二层,……,依此类推。
树的深度(depth):树中结点的最大层次数称为树的深度或高度。
有序树和无序树:如果树中各结点的子树是按照从左到右或者从右到左确定的,也就是说是有次序的,那么该树称为有序树,否则称为无序树。例如,家族树就是一棵有序树。
森林(forest):m(m≥0)棵互不相交的树的集合。由0棵树组成的森林成为空森林。
二叉树
二叉树是n(n≥0)个结点的有限集,它或为空树(n=0),或由一个根结点和两棵分别称为左子树和右子树的互不相交的二叉树构成。
1)每个结点至多有二棵子树(即不存在度大于2的结点)
2)二叉树的子树有左、右之分,且其次序不能任意颠倒
满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同一层上,这样的一棵二叉树称作满二叉树。
完全二叉树
一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同
二叉树性质
性质1 在二叉树第i层上至多有2i-1 个结点(i≥1)。
性质2 深度为k(k≥1)的二叉树至多有2k -1个结点。
性质3 对任何一颗二叉树T,如果其终端结点数为n0 ,度为2的结点数为n2 , 则n0 = n2 +1。
性质4 具有n个结点的完全二叉树的深度为 log2n+1 。
性质5 如果对一棵有n个结点的完全二叉树的结点按层序编号,则对任一结点i(1≤i≤n),有:
(1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则其双亲是i/2;
(2)如果2i>n,则结点i无左孩子;如果2i≤n,则其左孩子是2i;
(3)如果2i+1>n,则结点i无右孩子;如果2i+1≤n,则其右孩子是2i+1。
三叉链表的定义:
typedef struct BTNode
{TElemType data;
struct BTNode *lchild, *rchild, *parent;
}BTNode;
BTNode *bt;
二叉树的基本操作
(1)InitBTree(BT):初始化二叉树,构造空二叉树BT。
(2)DestoryBTree(BT):销毁二叉树。
(3)CreateBTree(BT):构造二叉树。
(4)ClearBTree(BT):将二叉树清空。
(5)IsEmptyBTree(BT):判断树是否为空。如果为空,返回true,否则返回false。
(6)GetBTreeDepth(BT):返回二叉树的深度。
(7)GetBTreeRoot(BT):求二叉树BT的根结点。
(8)GetParent(BT, current):求二叉树BT中结点current的双亲结点。若结点current是二叉树BT的根结点或者二叉树BT中无current结点,则函数值为“空”。
(9)GetLChild(BT, current):求二叉树BT中结点current的左孩子。若结点current是二叉树BT的叶子结点或者二叉树BT中无current结点,则函数值为“空”。
(10)GetRChild(BT, current):求二叉树BT中结点current的右孩子。若结点current是二叉树BT的叶子结点或者二叉树BT中无current结点,则函数值为“空”。
(11)InsertChild(BT, current, LR, child):将子树child插入到二叉树BT中,作为current结点的左子树(L)或者右子树(R)。
(12)DeleteChild(BT, current, LR):将二叉树BT的current结点的左子树(L)或者右子树(R)删除。
(13)PreTraverse(BT):先序遍历二叉树。
(14)InTraverse(BT):中序遍历二叉树。
(15)PostTraverse(BT):后序遍历二叉树。
(16)LevelTraverse(BT):层序遍历二叉树。按照从上到下、从左到右的顺序按层次遍历二叉树。
树转换成二叉树
方法:树中结点的孩子放在二叉树的左子树中,树中结点的兄弟放在二叉树的右子树中,简而言之就是,左孩子右兄弟。
转换步骤:
(1)在兄弟节点之间连一条线;
(2)每个结点仅保留其与最左孩子之间的连线,其余连线删除;
(3)以根为轴心将整棵树顺时针旋转45度。
树的孩子结点表示法
typedef struct chnode
{int child; //该结点在表头数组中下标
struct chnode *next; //指向下一孩子结点
}JD;
表头结点的存储结构如下:
typedef struct hnode
{elemtype data; //数据域
struct hnode *fc; //指向第一个孩子结点
TD;
TD t[M]; //t[0]不用
森林转换成二叉树
先把森林中的每一棵树转换成二叉树,然后从最后一棵树开始,把后一棵树作为前一棵树的右孩子。
二叉树的遍历
遍历:是指按照某种规律和顺序访问树中的全部结点,并且每个结点只访问一次。
遍历二叉树的目的:得到二叉树中各结点的一种线性顺序,使非线性的二叉树线性化,从而简化有关的运算和处理。
(1)先序(根)遍历(DLR)
若二叉树为空,则不访问任何结点;否则,访问顺序为:访问根结点;先序遍历左子树;先序遍历右子树。
(2)中序(根)遍历(LDR)
若二叉树为空,则不访问任何结点;否则,访问顺序为:中序遍历左子树; 访问根结点;中序遍历右子树。
(3)后序(根)遍历(LRD)
若二叉树为空,则不访问任何结点;否则,访问顺序为:后序遍历左子树; 后序遍历右子树;访问根结点。
先序遍历二叉树的递归算法如下:
void PreTraverse(BTNode *bt){
if(bt!=NULL) {
printf("%c",bt->data); /*访问根结点*/
PreTraverse(bt->lchild); /*访问左子树*/
PreTraverse(bt->rchild); /*访问右子树*/
}
printf("\n");
}
非递归算法
void PreTraverse(BTNode *bt){
if(bt!=NULL) {
p=t; 初始化栈,置为空;
while(栈非空||p!=NULL) {
if (p!=NULL) {
printf("%d",p->data);
if (栈不满) { 结点p入栈; p=p->lchild; }
else 栈满;
} else if(栈非空) {
栈顶元素出栈; p=p->rchild;
}
}
}
}
中序遍历二叉树的递归算法如下:
void InTraverse(Bnode *bt){
if(bt!=NULL) {
InTraverse(bt->lch); /*访问左子树*/
printf("%c",bt->data); /*访问根结点*/
InTraverse(bt->rch); /*访问右子树*/
}
printf("\n");
}
非递归算法
后序遍历二叉树的递归算法如下:
void LastTraverse(Bnode *bt){
if(bt!=NULL) {
LastTraverse(bt->lch); /*访问左子树*/
LastTraverse(bt->rch); /*访问右子树*/
printf("%c",bt->data); /*访问根结点*/
}
printf("\n");
}
二叉排序树定义
二叉排序树(Binary Sort Tree)或者是一棵空树;或者是具有下列性质的二叉树:
⑴ 若左子树不空,则左子树上所有结点的值均小于根结点的值;
若右子树不空,则右子树上所有结点的值均大于根结点的值。
⑵ 左右子树也都是二叉排序树。
中序遍历为有序数
以二叉链表作为二叉排序树的存储结构:
typedef struct NODE
{ ElemType elem; /*数据元素字段*/
struct NODE *lc,*rc; /*左、右指针字段*/
}NodeType; /*二叉树结点类型*/
二叉排序树的查找过程为:
① 若查找树为空,查找失败。
② 查找树非空,将给定值kx与查找树的根结点关键字比较。
③ 若相等,查找成功,结束查找过程,否则,
a.当给kx小于根结点关键字,查找将在以左子女为根的子树上继续进行,转①
b.当给kx大于根结点关键字,查找将在以右子女为根的子树上继续进行,转①
二叉排序树的生成
插入原则:若二叉排序树为空,则插入结点应为新的根结点;否则,继续在其左、右子树上查找,直至某个结点的左子树或右子树为空为止,则插入结点应为该叶子结点的左孩子或右孩子
二叉排序树生成:从空树出发,经过一系列的查找、插入操作之后,可生成一棵二叉排序树
二叉排序树的删除
原则:从二叉排序树中删除一个结点之后,使其仍能保持二叉排序树的特性即可
删除二叉排序树中的p结点的讨论
p为叶子结点,只需修改p双亲f的指针 f- >lc=NULL f->rc=NULL
p只有左子树或右子树
p只有左子树,用p的左孩子代替p (1)(2)
p只有右子树,用p的右孩子代替p (3)(4)
p左、右子树均非空
沿p左子树的根C的右子树分支找到S,S的右子树为空,将S的左子树成为S的双亲Q的右子树,用S取代p (5)
若C无右子树,用C取代p (6)
对给定序列建立二叉排序树,若左右子树均匀分布,则查找过程类似于有序表的折半查找
注意:若给定序列原本有序,则建立的二叉排序树就蜕化为单链表,查找效率同顺序查找一样。
平衡二叉树
平衡二叉树又称AVL树。它或者是一棵空树,或者是具有下列性质的二叉树:
(1)左子树和右子树深度之差的绝对值不超过1
(2)左右子树也分别是平衡二叉树
平衡二叉树的平衡因子:结点的左子树深度减去结点的右子树深度得到的值。
交换两个指针指向
#include<stdio.h>
void exchange(int **pp,int **qq){
int *ext=NULL;
ext=*pp;
*pp=*qq;
*qq=ext;
}
int main(){
int a=10;
int b=20;
int *p=&a;
int *q=&b;
printf("p=%p,q=%p\n",p,q);
exchange(&p,&q);
printf("p=%p,q=%p\n",p,q);
return 0;
}
学生管理系统
struct grade{
struct grade *prv;
struct grade *next;
struct class *cla;
}
struct class{
struct class *prv;
struct class *next;
struct student *stu;
}
typedef struct student{
struct student *prv;
struct student *next;
}stu;
数据结构基础整理(C语言)
最新推荐文章于 2024-07-31 17:24:32 发布