为什么学习数据结构
学习C语言是为了让大家如何去写程序
学习数据结构是为了让大家简洁、高效的去写程序
相关概念
数据结构研究的是数据的逻辑结构、存储结构及其操作
数据:计算机处理的对象(数据)已不再单纯是数值,更多的是一组数据
备注:一组数据称之为数据元素
逻辑结构:
1对1–>线性关系
1对多–>树形关系(1对2–>二叉树)
多对多–>网状关系–>图
存储结构:
顺序存储–>顺序表
链式存储–>链表
索引存储
哈希存储–>hash表
操作:创建、显示、删除、修改、查找(增、删、改、查)
线性表(1对1)
顺序表
顺序表的特点
1.顺序并且连续存储,访问方便
2.大小固定
3.表满不能存,表空不能取
优点:访问方便
缺点:插入、删除不方便都需要移动元素
定义数据元素的类型
假设做一个超市管理系统:
typedef struct shop
{
char name[20];//商品名称
float inprice;//进价
int count;//数量
float outprice;//售价
}data_type;
假设又做一个学生管理系统
typedef struct student
{
char name[];
int id;
int class;
float score;
}data_type;
typedef int/char data_type;
给原数据类型起别名,结构体属性不同,但都是元素集合
定义一个顺序表的类型
#define N 10
typedef struct list
{
data_type arr[N];//定义了一个data_type类型的数组
int count;//有效数据的个数
(0==count不能取,N==count不能存)
}List;
创建顺序表
//创建顺序表
//参数:void
//返回值:成功返回申请到的空间首地址,失败返回NULL
List * createList(void)
{
//定义一个指针变量接收malloc返回值
List *pList=NULL;
//动态申请
pList=(List *)malloc(sizeof(List));
if(NULL==pList)
{
perror("malloc error");
return NULL;
}
memset(pList,0,sizeof(List));
return pList;
}
向顺序表中插入元素
代码如下:
//向顺序表中插入数据
//参数1:顺序表首地址 List *pList
//参数2:要插入的位置 int pos
//参数3:要插入的数据 data_type item
//返回值:成功返回ok,失败返回失败原因
int insertList(List *pList,int pos,data_type item)
{
//1.入参判断
if(NULL==pList)
{
return LIST_NULL;
}
//2.判断顺序表是否为满
if(N==pList->count)
{
return FULL;
}
//3.判断插入位置是否正确
if(pos < 0 || pos > pList->count)
{
return POS_ERROR;
}
//4.向顺序表中插入数据
//从count-1的位置到pos的位置依次向后移动一位
int i;
for(i=pList->count-1;i>=pos;i--)
{
pList->arr[i+1]=pList->arr[i];
}
//将新值插入
pList->arr[pos]=item;
//5.有效数据+1
pList->count++;
return OK;
}
显示
//显示
//参数:顺序表的首地址 List *pList
//返回值:成功返回OK,失败返回失败原因
int showList(List *pList)
{
//1.入参判断
if(NULL ==pList)
{
return LIST_NULL;
}
//2.遍历
int i=0;
for(i=0;i<pList->count;i++)
{
printf("arr[%d]=%d\t",i,pList->arr[i]);
}
printf("\n");
return OK;
}
删除顺序表中元素
//删除顺序表中的元素
//参数1:顺序表的首地址 List *pList
//参数2:要删除的位置 int pos
//参数3:保存要删除的数据 data_type *pData
//返回值:成功返回OK,失败返回失败原因
int deleteList(List *pList,int pos,data_type *pData)
{
//1.入参判断
if(NULL ==pList)
{
return LIST_NULL;
}
//2.判断表是否为空
if(0==pList->count)
{
return EMPTY;
}
//3.判断删除的位置是否正确
if(pos < 0 || pos > pList->count-1)
{
return POS_ERROR;
}
//4.删除循序表中的元素
//4.1将要删除的数据保存起来
*pData=pList->arr[pos];
//4.2从pos+1的位置到count-1的位置依次向前移动一位
int i;
for(i=pos;i<pList->count-1;i++)
{
pList->arr[i]=pList->arr[i+1];
}
//5.有效数据-1
pList->count--;
return OK;
}
销毁顺序表
常用地址传递:
//销毁顺序表
//参数:顺序表的首地址 List **ppList
//返回值:成功返回OK,失败返回失败原因
int destroyList(List **ppList)
{
//1.入参判断
if(NULL == *ppList)
{
return LIST_NULL;
}
//2.释放空间
free(*ppList);
*ppList = NULL;
return OK;
}
值传递:
//销毁顺序表
//参数:顺序表的首地址 List *pList
//返回值:成功返回OK,失败返回失败原因
int destroyList(List *pList)
{
//1.入参判断
if(NULL ==pList)
{
return LIST_NULL;
}
//2.释放空间
free(pList);
return OK;
}
选用此种方法需在主函数内添加pList=NULL;
查找顺序表中元素数据
//查找数据
//参数1:顺序表首地址 List *pList
//参数2:要查找的数据 data_type odata
//返回值:成功返回OK,失败返回失败原因
int searchList(List *pList,data_type odata)
{
//1.入参判断
if(NULL ==pList)
{
return LIST_NULL;
}
int i;
int sum=0;
for(i=0;i<pList->count;)
{
if(odata==pList->arr[i])
{
printf("查找到元素为arr[%d]=%d\n",i,pList->arr[i]);
sum++;
}
i++;
}
if(sum==0)
{
printf("未查找到此数据\n");
return ERROR;
}
return OK;
}
修改顺序表中元素数据
//修改数据(调用查找函数)
//参数1:顺序表首地址 List *pList
//参数2:要修改的下标 int index
//参数3:修改后的数据 data_type ndata
//返回值:成功返回OK,失败返回失败原因
int alterList(List *pList,int index,data_type ndata)
{
//1.入参判断
if(NULL ==pList)
{
return LIST_NULL;
}
pList->arr[index]=ndata;
printf("更新成功\n");
return OK;
}
链表
链表的特点
1.申请空间可以不连续
2.访问不方便
3.插入、删除不需要移动元素
链表的相关概念
每一个结点不仅有数据域还有指针域
如何将不连续的空间连续起来?
前一个结点的指针域存放的是后一个结点的首地址
头结点:数据域无效的结点
首结点:第一个数据域有效的结点
尾结点:指针域为空的结点
空链表:既是头结点又是尾结点
链表的分类:
有没有头节点:
带头节点的链表、不带头节点的链表
指针域是双向还是单项:
单向链表、双向链表
尾结点是否指向头结点:
循环链表、不循环链表
定义数据元素的类型
typedef int data_type;
定义链表中结点的数据类型
typedef struct linkNode
{
data_type data;//数据域
struct linkNode *pNext;//指针域
}Link;
创建新节点
//创建链表
//参数:void
//返回值:成功返回申请到的空间首地址,失败返回NULL
Link * createLink(void)
{
//定义一个指针变量保存malloc的返回值
Link *pHead = (Link *) malloc(sizeof(Link));
//1.
if(NULL==pHead)
{
perror("malloc error");
return NULL;
}
//清空
memset(pHead,0,sizeof(Link));
//返回pHead
return pHead;
}
向链表中插入数据
头插法
尾插法
中间插
代码如下:
//向链表中插入数据
//参数1:链表的首地址 Link *pHead
//参数2:要插入的位置 int pos
//参数3:要插入的数据 data_type item
//返回值:成功返回OK,失败返回失败原因
int insertLink(Link *pHead,int pos,data_type item)
{
//1.入参判断
if(NULL==pHead)
{
return NULLERROR;
}
//2.创建新节点
Link *pNew = createLink();
//3.给新节点的数据域赋值
pNew->data = item;
//定义一个指针变量
Link *pTail = NULL;
Link *pMid = NULL;
int i=1;
switch(pos)
{
case HEAD:
//保护好要插入结点后的所有节点
pNew->pNext=pHead->pNext;
//将新节点插入进去
pHead->pNext=pNew;
break;
case TAIL:
//寻找尾结点(指针域为空)
//定义一个指针变量初始化为头结点
pTail=pHead;
while(pTail->pNext != NULL)
{
pTail=pTail->pNext;
}
//找到尾节点
//插入新节点
pTail->pNext = pNew;
break;
default:
//中间插入
pMid = pHead->pNext;
//找到要插入的结点
while(pMid != NULL && i<pos)
{
pMid = pMid->pNext;
i++;
}
pNew->pNext = pMid->pNext;
pMid->pNext = pNew;
break;
}
return OK;
}
显示链表数据
//显示链表数据
//参数:链表的首地址 Link *pHead
//返回值:成功返回OK,失败返回失败原因
int showLink(Link *pHead)
{
//1.入参判断
if(NULL==pHead)
{
return NULLERROR;
}
//2.定义一个指针变量,初始化为首节点
Link *pFind = pHead->pNext;
//3.当pFind不为空的情况下遍历链表元素
while(pFind!=NULL)
{
//访问数据
printf("%d ",pFind->data);
pFind = pFind->pNext;
}
printf("\n");
return OK;
}
根据位置删除链表中的数据
头删法
尾删法
中间删
代码如下:
//根据位置删除链表中的元素
//参数1:链表的首地址 Link *pHead
//参数2:int pos
//参数3:要保存的数据 data_type *pData
//返回值:成功返回OK,失败返回失败原因
int deleteLink(Link *pHead,int pos,data_type *pData)
{
//1.入参判断
if(NULL==pHead)
{
return NULLERROR;
}
//定义一个指针变量
Link *pDel = NULL;
Link *pPre = NULL;
int i=0;
//2.根据位置删除链表中的元素
switch (pos)
{
case HEAD:
//找到要删除的结点
pDel=pHead->pNext;
if(NULL == pDel)
{
return EMPTY;
}
//保存要删除的数据
*pData=pDel->data;
//保护好要删除节点后的所有节点
pHead->pNext = pDel->pNext;
//释放pDel
free(pDel);
pDel = NULL;
break;
case TAIL:
//定义两个指针一个初始化为头节点,一个初始化为首节点
pPre = pHead;
pDel = pHead->pNext;
//判断有无首节点
if(NULL == pDel)
{
return EMPTY;
}
//在pDel指向的结点指针域不为空情况下同时移动
while(pDel->pNext!=NULL)
{
pPre = pDel;
pDel = pDel->pNext;
}
//保存要删除的数据
*pData = pDel->data;
//将后一指针指向结点的指针域置空
pPre->pNext = NULL;
free(pDel);
//删除尾结点
pDel = NULL;
break;
default:
//定义两个指针一个初始化为头节点,一个初始化为首节点
pPre = pHead;
pDel = pHead->pNext;
//在pDel不为空的清空下移动pos-1次
while(pDel!=NULL&&i<pos-1)
{
pPre = pDel;
pDel = pDel->pNext;
i++;
}
if(NULL == pDel)
{
return POSERROR;
}
//保存要删除的数据
*pData = pDel->data;
//保护好要删除结点后的所有结点
pPre->pNext = pDel->pNext;
//释放pDel
free(pDel);
pDel = NULL;
break;
}
return OK;
}
销毁链表
//销毁链表
//参数:链表首地址的地址 Link **ppHead
//返回值:成功返回OK,失败返回失败原因
int destroyList(Link **ppHead)
{
//1.入参判断
if(NULL == *ppHead)
{
return NULLERROR;
}
Link *pDel = NULL;
//2.使用头删法的思想挨个删除链表中的结点,直到pDel为NULL
while(1)
{
//2.1找到要删除的结点
pDel = (*ppHead)->pNext;
if(NULL==pDel)
{
break;
}
//2.2保护好要删除结点的所有节点
(*ppHead)->pNext = pDel->pNext;
//释放pDel
free(pDel);
pDel = NULL;
}
//释放头结点
free(*ppHead);
*ppHead = NULL;
return OK;
}
带头节点的双向不循环链表
定义双向链表
//定义数据元素类型
typedef int data_type
//定义双向链表结点类型
typedef struct dblink
{
struct dblink *pPre;
data_type data;
struct dblink *pNext;
}dblink;
创建双向链表
dbLink * createdbLink(void)
{
dbLink * pHead=(dbLink *)malloc(sizeof(dbLink));
if(NULL==pHead)
{
return NULL;
}
memset(pHead,0,sizeof(dbLink));
return pHead;
}
向双向链表中插入数据
int insertdbLink(dbLink *pHead,int pos,data_type item)
{
if(NULL==pHead)
{
return NULLERROR;
}
dbLink *pNew = (dbLink *)malloc(sizeof(dbLink));
if(NULL==pNew)
{
perror("malloc error");
return MALLOCERROR;
}
memset(pNew,0,sizeof(dbLink));
pNew->data=item;
dbLink *pTmp = pHead;
int i=0;
while(pTmp!=NULL && i<pos)
{
pTmp=pTmp->pNext;
i++;
}
if(NULL == pTmp)
{
return POSERROR;
}
//先将新节点的两个指针域赋值
pNew->pNext=pTmp->pNext;
pNew->pPre=pTmp;
//先改变后一结点的前驱结点
if(NULL!=pTmp->pNext)
{
(pTmp->pNext)->pPre = pNew;
}
//后改变前一结点的后继结点
pTmp->pNext=pNew;
return OK;
}
显示双向链表中数据
int showdbLink(dbLink *pHead)
{
if(NULL == pHead)
{
return NULLERROR;
}
int i=0;
dbLink *pfind = pHead->pNext;
while(pfind != NULL)
{
printf("arr[%d]=%d ",i,pfind->data);
pfind=pfind->pNext;
i++;
}
printf("\n");
return OK;
}
删除双向链表中数据
int deletedbLink(dbLink *pHead,int pos,data_type *pitem)
{
if(NULL == pHead)
{
return NULLERROR;
}
if(pos == 0)
{
return POSERROR;
}
dbLink *pPel = pHead;
dbLink *pDel = pHead->pNext;
int i=0;
while(pDel!=NULL && i<pos-1)
{
pPel=pPel->pNext;
pDel=pDel->pNext;
i++;
}
if(NULL == pDel)
{
return NULLERROR;
}
*pitem = pDel->data;
if(pDel->pNext!=NULL)
{
(pDel->pNext)->pPre=pPel;
}
pPel->pNext=pDel->pNext;
free(pDel);
pDel=NULL;
return OK;
}
查询双向链表中数据
int searchdbLink(dbLink *pHead,data_type item)
{
if(NULL == pHead)
{
return NULLERROR;
}
dbLink *pfind = pHead->pNext;
int i=0;
int sum =0;
while(pfind!=NULL)
{
if(pfind->data == item)
{
printf("查找数据为arr[%d]=%d ",i,pfind->data);
sum++;
}
pfind=pfind->pNext;
i++;
}
if(sum == 0)
{
return ERROR;
}
printf("\n");
return OK;
}
修改双向链表中数据
int alterdbLink(dbLink *pHead,int pos,data_type item)
{
if(NULL == pHead)
{
return NULLERROR;
}
dbLink *pfind = pHead->pNext;
int i=0;
while(pfind != NULL && i<pos-1)
{
pfind=pfind->pNext;
i++;
}
if(NULL == pfind)
{
return POSERROR;
}
pfind->data=item;
return 0;
}
销毁双向链表
int destroydbLink(dbLink **ppHead)
{
if(NULL == *ppHead)
{
return NULLERROR;
}
dbLink *pfind = NULL;
while(1)
{
pfind = (*ppHead)->pNext;
if(NULL ==pfind)
{
break;
}
(*ppHead)->pNext=pfind->pNext;
free(pfind);
pfind = NULL;
}
free(*ppHead);
*ppHead = NULL;
return OK;
}
线性表的应用举例
约瑟夫环
多项式表示与相加
特殊的线性表
栈
栈的特征
1.栈是限制在一端(栈顶)进行插入操作和删除操作的线性表
2.先入后出
栈的应用
表达式计算过程中的应用
栈的存储
顺序存储
定义顺序栈
typedef int data_type;
typedef struct stack
{
data_type arr[SIZE];//保存数据元素SIZE个
int top;//保存栈顶的下标,-1为空栈,SIZE-1时满栈
}Stack;
创建顺序栈
Stack *createStack(void)
{
Stack* pStack = (Stack *)malloc(sizeof(Stack));
if(NULL == pStack)
{
printf("申请空间失败\n");
return NULL;
}
memset(pStack,0,sizeof(Stack));
pStack->top=-1;
return pStack;
}
入栈
int pushStack(Stack *pStack,data_type item)
{
if(NULL == pStack)
{
return NULL_ERROR;
}
//判断栈是否为满
if(pStack->top==SIZE-1)
{
return FULL;
}
//栈顶下标累加1
pStack->top++;
//将数据直接放到栈顶位置
pStack->arr[pStack->top]=item;
return OK;
}
出栈
int popStack(Stack *pStack,data_type * pitem)
{
if(NULL == pStack) return NULL_ERROR;
//判断栈是否为空
if(pStack->top==-1)
{
return EMPTY;
}
//将栈顶数据保存下来
*pitem=pStack->arr[pStack->top];
//栈顶下标累减1
pStack->top--;
return OK;
}
显示
int showStack(Stack *pStack)
{
if(NULL == pStack) return NULL_ERROR;
int i;
for(i=0;i<=pStack->top;i++)
{
printf("arr[%d]=%d ",i,pStack->arr[i]);
}
printf("\n");
return OK;
}
查找
int searchStack(Stack *pStack,data_type item)
{
if(NULL == pStack) return NULL_ERROR;
int i;
int sum=0;
for(i=0;i<=pStack->top;i++)
{
if(pStack->arr[i]==item)
{
printf("arr[%d]=%d ",i,pStack->arr[i]);
sum++;
}
}
if(sum==0)
{
return ERROR;
}
printf("\n");
return OK;
}
修改
int alterStack(Stack *pStack,int pos,data_type item)
{
if(NULL == pStack) return NULL_ERROR;
if(pos<0 || pos>pStack->top)
{
return POSE_ERROR;
}
pStack->arr[pos]=item;
return OK;
}
销毁
int destroyStack(Stack **pStack)
{
if(NULL == pStack) return NULL_ERROR;
free(*pStack);
*pStack=NULL;
return OK;
}
链式存储
队列
队列的特征
1.限制在两端进行操作的线性表,在队尾插入,在队头删除
2.先进先出
队列的应用
银行的叫号机:先入先出
队列的存储
顺序存储(循环队列)
定义
typedef int data_type;
typedef struct queue
{
data_type arr[SIZE];
int front;//队头的下标,顺序队列初始化为-1
int rear;//队尾的下标,顺序队列初始化为-1
//顺序队列front==rear相等时为空队,相差为1时为满队
//循环队列满队(rear+1)%SIZE == front
}Queue;
创建
Queue * createQueue(void)
{
Queue * pQueue=(Queue *)malloc(sizeof(Queue));
if(pQueue==NULL)
{
perror("申请空间失败");
return NULL;
}
memset(pQueue,0,sizeof(Queue));
pQueue->front=SIZE-1;//循环队列初始化为SIZE-1
pQueue->rear=SIZE-1;//循环队列初始化为SIZE-1
return pQueue;
}
入队
int EnQueue(Queue * pQueue,data_type item)
{
if(pQueue==NULL) return NULL_ERROR;
//判断队列是否为满
if((pQueue->rear+1)%SIZE==pQueue->front)
{
return FULL;
}
//更改队尾下标
pQueue->rear=(pQueue->rear+1)%SIZE;
//队尾下标处赋值
pQueue->arr[pQueue->rear]=item;
return OK;
}
出队
int DeQueue(Queue * pQueue,data_type * pitem)
{
if(pQueue==NULL) return NULL_ERROR;
//判断队列是否为空
if(pQueue->front==pQueue->rear)
{
return EMPTY;
}
//更改对头的下标
pQueue->front=(pQueue->front+1)%SIZE;
//将出队的数据保存下来
*pitem=pQueue->arr[pQueue->front];
return OK;
}
显示
int showQueue(Queue * pQueue)
{
int i;
for(i=0;i<=pQueue->rear;i++)
{
printf("arr[%d]=%d ",i,pQueue->arr[i]);
}
printf("\n");
return OK;
}
查找
int searchQueue(Queue * pQueue,data_type item)
{
if(NULL == pQueue) return NULL_ERROR;
int i;
int sum=0;
for(i=0;i<=pQueue->rear;i++)
{
if(pQueue->arr[i]==item)
{
printf("arr[%d]=%d ",i,pQueue->arr[i]);
sum++;
}
}
if(sum==0)
{
return ERROR;
}
printf("\n");
return OK;
}
修改
int alterQueue(Queue * pQueue,int pos,data_type item)
{
if(NULL == pQueue) return NULL_ERROR;
if(pos<0 || pos>SIZE-2)
{
return POSE_ERROR;
}
pQueue->arr[pos]=item;
return OK;
}
销毁
int destroyQueue(Queue ** pQueue)
{
if(NULL == pQueue) return NULL_ERROR;
free(*pQueue);
*pQueue=NULL;
return OK;
return OK;
}
树(1对多)
树
基本概念
树是n(n>0)个节点的有限集合T,满足两个条件:
1.有且仅有一个特定的成为根的节点
2.其余的节点可以分为m(m>=0)个互不相交的有限集合T1、T2···Tm,其中每一个集合又是一棵树,并成为其根的子树。
树的相关概念
度数
一个节点的子树的个数成为该节点的度数,一棵树的度数是指该树中节点的最大度数
度数为0的节点称为树叶或者终端节点,度数不为0的节点称为分支节点,除根节点之外的分支节点称为内部节点
深度
节点的层数等于父节点的层数加一,根节点的层数定义为一。树中节点层数的最大值称为该树的高度或深度
边数
一个节点系列k1,k2, ……,ki,ki+1, ……,kj,并满足ki是ki+1的父节点,就称为一条从k1到kj的路径,路径的长度为j-1,即路径中的边数
二叉树
定义
性质
满二叉树
深度为k(k≥1)时有2^k-1个节点的二叉树
完全二叉树
只有最下面两层有度数小于2的节点,且最下面一层的叶节点集中在最左边的若干位置上。
二叉树的存储
顺序存储
链式存储
定义
typedef int data_type;
typedef struct treeNode
{
struct treeNode * pLeft;//左子树的首地址
data_type data;
struct treeNode * pRight;//右子树的首地址
}biTree;
创建
biTree *createbiTree(data_type item)
{
biTree *pBoot = (biTree *)malloc(sizeof(biTree));
if (NULL == pBoot) return NULL;
memset(pBoot,0,sizeof(biTree));
pBoot->data=item;
return pBoot;
}
插入
int insertbiTree(biTree * pBoot,data_type item)
{
//判断树是否存在
if(NULL == pBoot) return TREENOEXIST;
//创建新的结点并将数据放入 pNew
biTree *pNew = (biTree *)malloc(sizeof(biTree));
if(NULL == pNew)
{
perror("malloc error");
return MALERR;
}
memset(pNew,0,sizeof(biTree));
pNew->data=item;
//将pNew->data与pBoot->data作比较
while(1)
{
//与左子树进行比较
if(pNew->data < pBoot->data)
{
if(pBoot->pLeft == NULL)
{
pBoot->pLeft = pNew;
return OK;
}
pBoot = pBoot->pLeft;
}
else//与右子树继续比较
{
if(pBoot->pRight == NULL)
{
pBoot->pRight = pNew;
return OK;
}
pBoot = pBoot->pRight;
}
}
return OK;
}
递归遍历
//遍历二叉树:
//前序
void preOrder(biTree * pBoot)
{
if(NULL == pBoot) return ;//直到为NULL结束
printf("%d",pBoot->data);//先访问根节点
preOrder(pBoot->pLeft);//访问左子树
preOrder(pBoot->pRight);//访问右子树
}
//中序
void inOrder(biTree * pBoot)
{
if(NULL == pBoot) return ;//直到为NULL结束
inOrder(pBoot->pLeft);//访问左子树
printf("%d",pBoot->data);//先访问根节点
inOrder(pBoot->pRight);//访问右子树
}
//后序
void postOrder(biTree * pBoot)
{
if(NULL == pBoot) return ;//直到为NULL结束
postOrder(pBoot->pLeft);//访问左子树
postOrder(pBoot->pRight);//访问右子树
printf("%d",pBoot->data);//先访问根节点
}
赫夫曼树
结点的带权路径长度指的是从树根到该结点的路径长度和结点上权的乘积。树的带权路径长度是指所有叶子节点的带权路径长度之和,记作 WPL 。WPL最小的二叉树就是最优二叉树,又称为赫夫曼树
赫夫曼编码
算法
程序 = 算法 + 数据结构
什么是算法
算法是一个有穷规则(或语句、指令)的有序集合
算法:解决问题的方法步骤
什么是程序
用计算机语言对算法的具体实现
算法和数据结构
算法的设计:取决于选定的逻辑结构(1对1(线性表)、1对多(树))
算法的实现:依赖于采用的存储结构(顺序存储、链式存储、索引存储、散列存储)
算法的特性
1.有穷性:算法执行的步骤(或规则)是有限的
2.确定性:每个计算步骤无二义性
3.可行性:每个计算步骤能够在有限的时间内完成
4.输入:算法有一个或多个外部输入
5.输出:算法有一个或多个输出
如何评判一个算法的好坏
消耗时间的多少(时间复杂度)
问题的规模 :输入数据量的大小,用n来表示。
算法的时间复杂度 :算法消耗时间,它是问题规模的函数 T(n)
计算大O的方法:
(1).根据位置规模n写出表达式 f(n)=n^2/2+n/2
(2).如果有常数项,将其置为1 (当f(n)表达式中只有常数项的时候,有意义)
(3).只保留最高项,其他项舍去 f(n)=n^2/2
(4).如果最高项系数不为1,将其置为1 f(n)=n^2
T(n)=O(n^2) ---->平方级
T(n)的量级:
消耗存储空间的多少(空间复杂度)
容易理解、容易编程和调试、容易维护
查找算法
顺序查找
例如数组的全体数据遍历–> t(n)=O(n)线性级
二分查找/折半查找
必须在有序序列当中查找
#include <stdio.h>
//编写一子函数,实现折半查找,必须是在有序序列中
//参数1:序列的首地址
//参数2:元素的个数
//参数3:你要查找的数据
//返回值:成功返回所在的下标,失败返回-1
int biSearch(int arr[],int n,int item)
{
int low=0; //序列中的最小值
int high=n-1;//序列中的最大值
int mid=0;
while(low<=high)
{
mid=(low+high)/2; //mid 为两者之和除2
if(arr[mid]==item)
{
return mid;//找到了返回所在下标
}else if(arr[mid]>item) //表示你找的在左半边
{
high=mid-1;
}else if(arr[mid]<item)//表示你找的在右半边
{
low=mid+1;
}
}
return -1;//找不到返回-1
}
//二分查找时间复杂度是对数级别 T(n)=O(__lgn)
哈希查找
什么是ASL
一般以“平均查找长度”来衡量T(n)。
平均查找长度ASL(Average Search Length)
为什么要使用hash查找
理想的查找方法是:对给定的k,不经任何比较便能获取所需的记录,其查找的时间复杂度为常数级O©。这就要求在建立记录表的时候,确定记录的key与其存储地址之间的关系f,即使key与记录的存放地址H相对应:
或者说,记录按key存放。
hash函数选取
1.直接地址法
2.平均取中法
3.叠加法
保留余数法/质数除余法
又称质数除余法,设Hash表空间长度为m
选取一个不大于m的最大质数p,令: H(key)=key%p
解决冲突办法
线性探查法
链地址法
Hash相关操作
创建
插入
查找
案例:学生成绩管理系统
头文件:
#ifndef _INCLUDE_H
#define _INCLUDE_H
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//定义结构体数据类型
typedef struct information
{
char name[20];
int score;
char grade;
}data_type;
//定义链表结点数据类型
typedef struct linknode
{
data_type data;
struct linknode *pNext;
}LinkNode;
//定义hash表数据类型
typedef struct hash
{
LinkNode **arr;
int count;
}Hash;
//定义枚举
enum RES
{
HASHLINK_NO_EXIT,
DATA_EXIST,
DATA_NO_EXIST,
MALERR,
FOPEN_ERROR,
OK
};
void Menu(void);
int HashFun(char key);
Hash * CreateHash(int size);
int InsertHash(Hash * pHash,data_type item);
int ShowHash(Hash * pHash);
int SearchHash(Hash * pHash,char * name);
int AlterHash(Hash * pHash,char * name,int score);
int DeleteHash(Hash * pHash,char * name,data_type * pdata);
int DestroyHash(Hash ** ppHash);
int SaveFile(Hash * pHash,data_type * pStu);
int LoadFile(Hash * pHash,data_type * pStu);
#endif
功能函数:
#include "../include/include.h"
void Menu(void)
{
printf(" *****学生成绩管理系统***** \n");
printf(" ---输入选项--- \n");
printf("| 1------创建------ |\n");
printf("| 2------插入------ |\n");
printf("| 3------显示------ |\n");
printf("| 4------查找------ |\n");
printf("| 5------修改------ |\n");
printf("| 6------删除------ |\n");
printf("| 7------保存------ |\n");
printf("| 8------下载------ |\n");
printf("| 9------销毁------ |\n");
printf("| -1---退出--- |\n");
}
int HashFun(char key)
{
int sum = 0;
sum=key-'a';
if(sum<0) sum = -1*sum;
return sum;
}
Hash * CreateHash(int size)
{
Hash * pHash=(Hash *)malloc(sizeof(Hash));
if(NULL == pHash)
{
perror("哈希表创建失败!");
return NULL;
}
memset(pHash,0,sizeof(Hash));
pHash->arr = (LinkNode **)malloc(size*sizeof(LinkNode *));
if(NULL == pHash->arr)
{
perror("空间申请失败!");
return NULL;
}
printf("创建成功!\n");
pHash->count = size;
return pHash;
}
int InsertHash(Hash * pHash,data_type item)
{
if(NULL == pHash) return HASHLINK_NO_EXIT;
if(OK == SearchHash(pHash,item.name)) return DATA_EXIST;
LinkNode * pNew = (LinkNode *)malloc(sizeof(LinkNode));
if(NULL == pNew) return MALERR;
memset(pNew,0,sizeof(LinkNode));
pNew->data = item;
int pos = HashFun(item.name[0]);
pNew->pNext = pHash->arr[pos];
pHash->arr[pos] = pNew;
return OK;
}
int ShowHash(Hash * pHash)
{
if(NULL == pHash) return HASHLINK_NO_EXIT;
int i;
for(i=0;i<pHash->count;i++)
{
LinkNode * pTmp = pHash->arr[i];
while(NULL != pTmp)
{
printf("%s\t%d\t%c\n",pTmp->data.name,pTmp->data.score,pTmp->data.grade);
pTmp = pTmp->pNext;
}
}
return OK;
}
int SearchHash(Hash * pHash,char *name)
{
if(NULL == pHash) return HASHLINK_NO_EXIT;
int pos = HashFun(name[0]);
LinkNode * pTmp = pHash->arr[pos];
int i=0;
while(NULL != pTmp)
{
if(strcmp(pTmp->data.name,name)==0)
{
printf("%s\t%d\t%c\n",pTmp->data.name,pTmp->data.score,pTmp->data.grade);
i++;
}
pTmp = pTmp->pNext;
}
if(0 == i) return DATA_NO_EXIST;
return OK;
}
int AlterHash(Hash * pHash,char *name,int score)
{
if(NULL == pHash) return HASHLINK_NO_EXIT;
int pos = HashFun(name[0]);
LinkNode * pTmp = pHash->arr[pos];
while(NULL != pTmp)
{
if(strcmp(pTmp->data.name,name)==0)
{
pTmp->data.score=score;
printf("%s\t%d\t%c\n",pTmp->data.name,pTmp->data.score,pTmp->data.grade);
}
pTmp = pTmp->pNext;
}
return OK;
}
int DeleteHash(Hash * pHash,char *name,data_type * pdata)
{
if(NULL == pHash) return HASHLINK_NO_EXIT;
int pos = HashFun(name[0]);
LinkNode * pDel = pHash->arr[pos];
LinkNode *pPre = NULL;
if(strcmp(pDel->data.name,name)==0)
{
*pdata=pDel->data;
pHash->arr[pos] = pDel->pNext;
free(pDel);
pDel = NULL;
return OK;
}
while(1)
{
if(strcmp(pDel->data.name,name)==0 && pDel->pNext==NULL )
{
*pdata=pDel->data;
pPre->pNext = NULL;
free(pDel);
pDel = NULL;
return OK;
}
else if(strcmp(pDel->data.name,name)==0)
{
*pdata=pDel->data;
pPre->pNext = pDel->pNext;
free(pDel);
pDel = NULL;
return OK;
}
pPre = pDel;
pDel = pDel->pNext;
}
}
int DestroyHash(Hash ** ppHash)
{
if(NULL == *ppHash) return HASHLINK_NO_EXIT;
int i;
LinkNode *pHead = NULL;
LinkNode *pDel = NULL;
for(i = 0; i < (*ppHash)->count ; i++)
{
pHead = (*ppHash)->arr[i];
while(NULL != pHead)
{
//头删法
pDel = pHead;
pHead = pHead->pNext;
free(pDel);
pDel = NULL;
}
}
free(*ppHash);
*ppHash = NULL;
return OK;
}
int SaveFile(Hash * pHash,data_type * pStu)
{
if(NULL == pHash) return HASHLINK_NO_EXIT;
if(NULL == pStu) return MALERR;
FILE *fw = fopen("stu.txt","w");
if(NULL==fw) return FOPEN_ERROR;
int i=0;
for(i=0;i<pHash->count;i++)
{
LinkNode * pTmp = pHash->arr[i];
while(NULL != pTmp)
{
pStu=&(pTmp->data);
fwrite(pStu,1,sizeof(data_type),fw);
printf("保存的学生信息为:姓名:%s\t成绩:%d\t评级:%c\n",pStu->name,pStu->score,pStu->grade);
pTmp = pTmp->pNext;
}
}
fclose(fw);
return OK;
}
int LoadFile(Hash * pHash,data_type * pStu)
{
if(NULL == pHash) return HASHLINK_NO_EXIT;
if(NULL == pStu) return MALERR;
FILE *fr = fopen("stu.txt","r");
if(NULL==fr) return FOPEN_ERROR;
while(1)
{
fread(pStu,1,sizeof(data_type),fr);
if(feof(fr)) break;
printf("下载的学生信息为:姓名:%s\t成绩:%d\t评级:%c\n",pStu->name,pStu->score,pStu->grade);
}
fclose(fr);
return OK;
}
主函数:
#include "../include/include.h"
int main(int argc, char const *argv[])
{
int op;
int size =0;
data_type item;
data_type data;
Hash *pHash = NULL;
data_type * pStu = (data_type *)malloc(sizeof(data_type));
if(NULL == pStu)
{
printf("malloc_error\n");
return MALERR;
}
system("clear");
while(1)
{
Menu();
scanf("%d",&op);
if(-1==op)
{
system("clear");
break;
}
switch (op)
{
case 1:
system("clear");
printf("请输入hash表的最大尺寸\n");
scanf("%d",&size);
system("clear");
pHash = CreateHash(size);
if(pHash == NULL)
{
printf("创建失败!\n");
}
break;
case 2:
system("clear");
printf("请输入学生姓名与成绩\n");
scanf("%s%d",item.name,&item.score);
switch(item.score/10)
{
case 10:
item.grade = 'S';
break;
case 9:
item.grade = 'A';
break;
case 8:
item.grade = 'B';
break;
case 7:
item.grade = 'C';
break;
case 6:
item.grade = 'D';
break;
default:
item.grade = 'E';
break;
}
system("clear");
if(OK!=InsertHash(pHash,item))
{
printf("插入失败!\n");
}
else
{
printf("插入成功!\n");
}
break;
case 3:
system("clear");
if(OK!=ShowHash(pHash))
{
printf("显示失败!\n");
}
else
{
printf("显示成功!\n");
}
break;
case 4:
system("clear");
printf("请输入查找的学生姓名\n");
scanf("%s",item.name);
system("clear");
if(OK != SearchHash(pHash,item.name))
{
printf("查找失败!\n");
}
else
{
printf("查找成功!\n");
}
break;
case 5:
system("clear");
printf("请输入要修改的姓名\n");
scanf("%s",item.name);
system("clear");
if(OK!=SearchHash(pHash,item.name))
{
printf("查找失败!\n");
}
else
{
printf("查找成功!\n");
printf("请输入修改后的分数\n");
scanf("%d",&item.score);
system("clear");
if(OK!=AlterHash(pHash,item.name,item.score))
{
printf("更新失败!\n");
}
else
{
printf("更新成功!\n");
}
}
break;
case 6:
system("clear");
printf("请输入要删除的姓名\n");
scanf("%s",item.name);
system("clear");
if(OK != SearchHash(pHash,item.name))
{
printf("查找失败!\n");
}
else
{
printf("查找成功!\n");
if(OK != DeleteHash(pHash,item.name,&data))
{
printf("删除失败!\n");
}
else
{
printf("删除的数据为 %s\t%d\t%c\n",data.name,data.score,data.grade);
printf("删除成功!\n");
}
}
break;
case 7:
system("clear");
if(OK != SaveFile(pHash,pStu))
{
printf("保存失败!\n");
}
else
{
printf("保存成功!\n");
}
break;
case 8:
system("clear");
if(OK != LoadFile(pHash,pStu))
{
printf("下载失败!\n");
}
else
{
printf("下载成功!\n");
}
break;
case 9:
system("clear");
printf("释放前:%p\n",pHash);
if(OK!=DestroyHash(&pHash))
{
printf("释放后:%p\n",pHash);
printf("销毁失败\n");
}
else
{
printf("释放后:%p\n",pHash);
printf("销毁成功!\n");
}
break;
}
}
return 0;
}
排序算法
选择排序
插入排序
快速排序(递归思想)
#include<stdio.h>
//功能:将序列中第一个数作为基准,比他大的放右边,比他小的放左边
//参数1:序列中首地址
//参数2:序列中最小的下标
//参数3:序列中最大的下标
//返回值:返回基准值所在的下标
int quick(int arr[],int low,int high)
{
//1.将序列中的第一个数作为基准
int tmp = arr[low];
while(low<high)
{
//2.从上界开始查找第一个比它小的数,放入到下界
while(low<high)
{
if(arr[high]<tmp) break;
high--;
}
arr[low]=arr[high];
//从下界开始查找第一个比它大的数,放入到上界
while(low<high)
{
if(arr[low]>tmp) break;
low++;
}
arr[high]= arr[low];
}
//4.将基准放入到low中
arr[low]=tmp;
return low;
}
//功能:快速排序
//参数1:序列中首地址
//参数2:序列中最小的下标
//参数3:序列中最大的下标
void quicksort(int arr[],int low,int high)
{
if(low>=high) return;
//1.将序列中第一个数作为基准,比他大的放右边,比他小的放左边
int mid =quick(arr,low,high);
//2.使用同样的办法将序列中左边的值进行排序
quicksort(arr,low,mid-1);
//3.使用同样的办法将序列中右边的值进行排序
quicksort(arr,mid+1,high);
}
void output(int arr[],int n)
{
int i=0;
for(i=0;i<n;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
int main(void)
{
int arr[10]={54,12,67,45,89,34,58,41,90,78};
output(arr,10);
quicksort(arr,0,9);
output(arr,10);
return 0;
}#include<stdio.h>
//功能:将序列中第一个数作为基准,比他大的放右边,比他小的放左边
//参数1:序列中首地址
//参数2:序列中最小的下标
//参数3:序列中最大的下标
//返回值:返回基准值所在的下标
int quick(int arr[],int low,int high)
{
//1.将序列中的第一个数作为基准
int tmp = arr[low];
while(low<high)
{
//2.从上界开始查找第一个比它小的数,放入到下界
while(low<high)
{
if(arr[high]<tmp) break;
high--;
}
arr[low]=arr[high];
//从下界开始查找第一个比它大的数,放入到上界
while(low<high)
{
if(arr[low]>tmp) break;
low++;
}
arr[high]= arr[low];
}
//4.将基准放入到low中
arr[low]=tmp;
return low;
}
//功能:快速排序
//参数1:序列中首地址
//参数2:序列中最小的下标
//参数3:序列中最大的下标
void quicksort(int arr[],int low,int high)
{
if(low>=high) return;
//1.将序列中第一个数作为基准,比他大的放右边,比他小的放左边
int mid =quick(arr,low,high);
//2.使用同样的办法将序列中左边的值进行排序
quicksort(arr,low,mid-1);
//3.使用同样的办法将序列中右边的值进行排序
quicksort(arr,mid+1,high);
}
void output(int arr[],int n)
{
int i=0;
for(i=0;i<n;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
int main(void)
{
int arr[10]={54,12,67,45,89,34,58,41,90,78};
output(arr,10);
quicksort(arr,0,9);
output(arr,10);
return 0;
}