数据结构概述
定义:
我们如何把现实中大量而复杂的问题以特定的数据类型和特定的存储结构保存到主存储器(内存)中,以及在此基础上为实现 某个功能(比如查找某个元素,删除某个元素,对所有元素进行排序)而执行的相应操作,这个相应操作也叫算法。
数据结构 = 个体 + 个体的关系
算法 = 对存出数据的操作
对汇编来说 数据结构就是代码段;算法就是指令;
算法
解题的方法和步骤
衡量算法的标准
1.时间复杂度(大概程序要执行的次数,而非执行的时间:(因为可能因为硬件的区别而导致运行时间的快慢))
2.空间复杂度:算法执行过程中大概所占用的最大内存
3.难易程度;
4.健壮性:(当出现输入错误之类的受影响程度)
数据结构的地位:
数据结构是软件中最核心的课程,
程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言
预备知识
指针 结构体 动态内存的分配和释放
模块一:线性结构
连续存储 [ 数组 ]
离散存储 [ 链表 ] :单链表;双链表;循环链表,非循环链表
线性结构的两种常见应用之一 栈
线性结构的两种常见应用之二 队列
专题 :递归
递归就是直接调用自己;如果只递不归就相当于死循环但又不是;每次调用函数都会在栈中保存数据;如果一直递归进行中;最后会造成栈溢出;fbnq0
链表:
定义:
n个结点离散分配
彼此通过指针相连
每个结点都只有一个前驱结点,每个结点只有一个后续结点
首结点没有前驱结点,尾节点没有后续结点
专业术语:
首结点:第一个有效结点
尾结点:最后一个有效结点
头结点:一般来说我们会在首结点的前面有一个不存放有效数据的头结点(是为了方便我们对链表进行操作):第一个有效节点前那个结点
头指针:指向头节点的指针变量()
尾指针:指向尾节点的指针
头结点的数据类型和其他结点一样
头指针:指向头结点的指针
尾指针:指向尾结点的指针
如果希望通过一个函数来对链表进行处理,我们至少需要接受链表的哪些信息:
·只要一个参数:头指针(可以推算出链表的所有信息)
双链表:每一个结点有两个指针域
循环链表:可以通过任意一个结点找到其他所有结点
详细去看c语言笔记
插入结点:
q->next = p->next;
p->next = q;
p->pNext; //p所指向结构体变量中的pNext成员本身
代码:
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <stdbool.h>
//—————————————————————————————————————————————————————————————————————————结构体定义———————————————————————————————
typedefstructNODE
{
intdate; //数据域
structNODE*pNext; //指针域
}NODE,*PNODE; //NODE等价于struct NODE;PNODE等价于struct NODE*
//——————————————————————————————————————————————————————————————————函数声明 ———————————————————————————————————————
PNODEcreate_list(void); //创建一个链表,并将头结点的地址返回
voidtraverse_list(PNODEpHead); //遍历链表中的数据域
boolis_empty(PNODEpHead); //检查链表是否为空
intlength_list(PNODE); //检查链表长度
boolinsert_list(PNODE,int,int); //
booldelete_list(PNODE,int,int*); // 链表结点的删除;第二个参数用来表明删除第几个结点;第三个参数用来存被删除结点的地址
void soert_list(PONDE); //链表的排序;
//———————————————————————————————————————————————————————————————————主函数 ————————————————————————————————————————
intmain(void)
{
intval;
intpos;
intval1;
PNODEpHead=NULL; //pHead为头指针
pHead=create_list(); //创建一个非循环单链表用然后将头节点的地址返回
traverse_list(pHead); //遍历一个链表
intlen=length_list(pHead);
printf("链表长度是%d\n",len);
/*
soert_list(pHead);
traverse_list(pHead);
printf("请输入要添加的数据");
scanf("%d",&val);
printf("请输入要插入到第几个结点");
scanf("%d",&pos);
if(pos>len+2)
{
printf("插入错误");
exit(-1);
}
insert_list(pHead,pos,val);
traverse_list(pHead);
*/
if(delete_list(pHead,4,&val1))
{
printf("删除成功,您删除的元素是:%d\n",val1);
}
else
{
printf("删除失败,您删除的元素不存在\n");
}
traverse_list(pHead);
if (is_empty(pHead))
printf("链表为空");
else
printf("链表不为空");
free(pHead);
return0;
}
/*————————————————————————————————————————————————函数定义————————————————————————————————————————————————————————*/
PNODEcreate_list(void)
{
intlen; // 用来存放有效结点的个数
inti;
intval; //用来临时存放用户输入的结点的值
PNODEpHead= (PNODE)malloc(sizeof(NODE)); //分配了一个不存放有效数据的头节点
if(NULL==pHead)
{
printf("分配失败,程序终止\n");
exit(-1);
}
PNODEpTail=pHead; //尾指针现在指向头结点
pTail->pNext=NULL; //使其指向空;
printf("请输入您要生成的链表结点个数 len =");
scanf("%d",&len);
for(i=0;i<len;i++)
{
printf("请输入第%d个结点的值",i+1);
scanf("%d",&val);
PNODEpNew= (PNODE)malloc(sizeof(NODE)); //创建新的结点
if(NULL==pHead) //判断是否创建成功
{
printf("分配失败,程序终止\n");
exit(-1);
}
pNew->date=val; //将val保存到新结点
pTail->pNext=pNew; //使用尾指针将上一个结点的指针域指向新尾结点
pNew->pNext=NULL; //新尾结点的指针域悬空
pTail=pNew; //尾指针指向新尾结点
}
returnpHead; //返回头结点地址,
}
voidtraverse_list(PNODEpHead)
{
PNODEP=pHead->pNext;
for(;P;P=P->pNext)
{
printf("%d\n",P->date);
}
}
boolis_empty(PNODEpHead)
{
boola;
if(NULL==pHead->pNext)
a=true;
else
a=false;
returna;
}
intlength_list(PNODEpHead)
{
intnum=0;
PNODEp=pHead->pNext;
while(p)
{
num++;
p=p->pNext;
}
returnnum;
}
voidsoert_list(PNODEpHead)
{
inti,j,t;
PNODEp,q;
intlen=length_list(pHead);
for(i=0,p=pHead->pNext;i<len-1;p=p->pNext,i++)
{
for(j=i+1,q=p->pNext;j<len;q=q->pNext,j++)
{
if(p->date>q->date) //数组中的a[i]>a[j]
{ //数值交换
t=p->date; // t = a[i];;
p->date=q->date; // a[i] = a[j];
q->date=t; // a[j] = t;
}
}
}
}
//在pHead所指的链表的第pos个结点的前面插入一个新的结点,该节点的值是val
boolinsert_list(PNODEpHead,intpos,intval)
{
// int len = length_list(pHead);
// PNODE New = (PNODE)malloc(sizeof(NODE));
// PNODE p,q;
// p = pHead->pNext;
// q = p->pNext;
// if(pos==1) //首结点插入
// {
// New->date = val;
// pHead->pNext =New;
// New->pNext = p;
// }
// if(pos==len+1) //尾结点添加
// {
// while(q->pNext)
// {
// p=q;
// q=q->pNext;
// if(q->pNext==NULL)
// {
// q->pNext = New;
// New->date = val;
// New->pNext = NULL;
// }
// }
// }
// pos--;
// while(p->pNext) //普通结点插入
// {
// pos--;
// if(pos==0)
// {
// New->date = val;
// p->pNext = New;
// New->pNext = q;
// }
// p = q;
// q = q->pNext;
// }
inti=0;
PNODEp=pHead;
while(NULL!=p&&i<pos-1)
{
p=p->pNext;
++i;
}
if(i>pos-1||NULL==p)
{
returnfalse;
}
PNODEpNew= (PNODE)malloc(sizeof(NODE));
if(NULL==pNew)
{
printf("分配失败");
exit(-1);
}
pNew->date=val;
PNODEq=p->pNext;
p->pNext=pNew;
pNew->pNext=q;
returntrue;
}
booldelete_list(PNODEpHead,intpos,int*pVal)
{
inti=0;
PNODEp=pHead;
while(NULL!=p->pNext&&i<pos-1){
p=p->pNext;
++i;
}
if(i>pos-1||NULL==p->pNext)
{
returnfalse;
}
PNODEq=p->pNext;
p->pNext=q->pNext;
*pVal=q->date;
//删除p结点后面的结点
free(q);
q=NULL;
returntrue;
}
算法:
狭义的算法是与数据存储的方式密切相关
广义的算法是与数据的存储方式无关
泛型:
利用某种技术达到的效果就是:不同的存数方式,执行的操作一样;
栈
一种可以实现先进后出的存储结构
栈的分类:
动态栈;
静态栈;
算法
出栈
压栈
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#include<stdbool.h>
/*——————————————————————————结构体定义————————————————————————————————————*/
typedefstructNode{
intdate;
structNode*pNext;
}NODE, *PNODE;
typedefstructStack{
PNODEpTop; //栈顶指针
PNODEpBottom; //栈底指针
}STACK,*PSTACK;
/*------------------------------------------------函数声明--------------------------------------------------------------------*/
voidinit(PSTACK);
voidpush(PSTACK,int);
voidtraverse(PSTACK);
boolpop(PSTACK,int*);
boolclear(PSTACK);
boolemtyp(PSTACK);
voidf(PSTACK);
/*-_-------------------------------------------------主函数---------------------------------------------------------------------*/
intmain(void)
{
STACKS; //STACK等价于struct Stack
intval;
f(&S);
init(&S);
push(&S,1);
push(&S,2);
push(&S,3);
push(&S,4);
push(&S,5);
traverse(&S);
f(&S);
if(pop(&S,&val))
{
printf("被弹出的值为%d\n",val);
}else{
printf("弹出失败\n");
}
traverse(&S);
if(clear(&S))
{
printf("栈已清空\n");
}
else
{
printf("函数失败\n");
}
traverse(&S);
f(&S);
return0;
}
/*-------------------------------------------------函数定义-----------------------------------------------------------------------*/
voidinit(PSTACKpS)
{
PNODEHead= (PNODE)malloc(sizeof(NODE));
if(NULL==Head)
{
printf("动态内存分配失败\n");
exit(-1);
}
else
{
pS->pTop=Head;
pS->pBottom=Head;
pS->pTop->pNext=NULL;
}
}
voidpush(PSTACKpS,intval)
{
PNODENew= (PNODE)malloc(sizeof(NODE));
New->date=val;
New->pNext=pS->pTop;
pS->pTop=New;
return;
}
voidtraverse(PSTACKpS)
{
PNODEP;
for(P=pS->pTop;P->pNext;P=P->pNext)
printf("%d\n",P->date);
}
boolpop(PSTACKpS,int*p)
{
if(pS->pTop!=pS->pBottom)
{
PNODEpr=pS->pTop; //定义一个辅助参数
*p=pr->date; //保存被弹出的值
pS->pTop=pr->pNext; //将栈顶指针向下
free(pr);
pr=NULL;
returntrue;
}
else
{
printf("栈已空\n");
returnfalse;
}
}
boolclear(PSTACKPS)
{
PNODEP; //辅助参数,用来释放内存;
while(PS->pTop!=PS->pBottom)
{
P=PS->pTop;
PS->pTop=P->pNext;
free(P);
}
P=NULL;
if(PS->pTop==PS->pBottom)
{
returntrue;
}
else
{
returnfalse;
}
}
boolemtyp(PSTACKPS)
{
if(PS->pBottom==PS->pTop)
{
returntrue;
}
else
{
returnfalse;
}
}
voidf(PSTACKPS)
{
if(emtyp(PS))
{
printf("栈为空\n");
}
else
{
printf("栈不为空\n");
}
}
队列
定义:一种可以实现“先进先出”的存储结构
分类
· 链式队列:----用链表实现
·静态队列(数组队列)----------用数组实现
1.静态队列为什么必须是循环队列
2.循环队列需要几个参数来确定
一个队列要两个参数来确定:1.front 2.rear(2个参数不同场合有不同含义)
建议初学者先记住,后面慢慢体会
3.循环队列各个参数的含义
2个参数不同场合有不同含义
(1)队列初始化
front和rear的值都是零;
(2)队列非空
front代表的是队列的第一个元素
rear代表的的是最后一个队列有效元素的下一个元素
(3)队列空
front和rear相等,但不一定是零
4.循环队列入队伪算法讲解
两步完成
1.将值存入r所代表的位置
2.错误的写法rear = rear + 1;正确的写法rear = (rear + 1)%数组的长度
5.循环队列出队伪算法讲解
1.front = (front + 1)%数组的长度
6.如何判断循环队列是否为空;
如果front和rear的值相等,则该队列的值就一定为空
7.如何判断循环队列是否已满;
预备知识
front的值可能比rear大,也可能比rear小,当然也可能相等
两种方式:
1. 增加一个标识参数
2. 少用一个元素(通常使用第二种方式)
如果rear和front紧挨着,则队列已满
if((r + 1)%数组长度== f)
已满
else
不满
队列算法
入队:
出队:
代码
//循环队列的算法
#include<stdio.h>
#include<stdbool.h>
#include<malloc.h>
/*——————————————————————————————————————结构体定义————————————————————————————————————————————————*/
typedefstructQueue
{
int*pBase;
intrear;
intfront;
}QUEUE;
/*——————————————————————————————————————函数声明——————————————————————————————————————————————————*/
voidinit(QUEUE*); //初始化队列
boolen_queue(QUEUE*, intval); //入队;添加值
voidtraverse_queue(QUEUE*); //遍历输出
boolfull_queue(QUEUE*); //判断队列是否已满
boolout_queue(QUEUE*,int*pVal);
boolemput_queue(QUEUE*);
/*————————————————————————————————————————主函数——————————————————————————————————————————————————*/
intmain()
{
QUEUEQ;
intval;
init(&Q);
en_queue(&Q, 1);
en_queue(&Q, 2);
en_queue(&Q, 3);
en_queue(&Q, 4);
en_queue(&Q, 5);
en_queue(&Q, 6);
en_queue(&Q, 7);
en_queue(&Q, 8);
traverse_queue(&Q);
if (out_queue(&Q, &val))
{
printf("出队成功,出队的元素是%d\n",val);
}
else
{
printf("出队失败!");
}
traverse_queue(&Q);
return0;
}
/*——————————————————————————————————————函数定义——————————————————————————————————————————————————*/
voidinit(QUEUE*pQ)
{
pQ->pBase= (int*)malloc(sizeof(int)*6);
pQ->front=0;
pQ->rear=0;
}
boolen_queue(QUEUE*pQ, intval)
{
if (full_queue(pQ))
{
returnfalse;
}
else
{
pQ->pBase[pQ->rear] =val;//存数
pQ->rear= (pQ->rear+1) %6 ;
returntrue;
}
}
boolfull_queue(QUEUE*pQ)
{
if ((pQ->rear+1) %6==pQ->front)
returntrue;
else
returnfalse;
}
voidtraverse_queue(QUEUE*pQ)
{
inti=pQ->front;
while (i!=pQ->rear)
{
printf("%d ",pQ->pBase[i]);
i= (i+1) %6;
}
printf("\n");
}
boolout_queue(QUEUE*pQ, int*pVal)
{
if (emput_queue(pQ))
{
returnfalse;
}
else
{
*pVal=pQ->pBase[pQ->front];
pQ->front= (pQ->front+1) %6;
returntrue;
}
}
boolemput_queue(QUEUE*pQ)
{
if (pQ->front==pQ->rear)
{
returntrue;
}
else
{
returnfalse;
}
}
队列的具体应用:
所有和时间和有关的操作都有队列的影子;
递归
定义:一个函数直接或者间接调用自己
递归要满足的三个条件:
1. 递归必须有一个明确的中止条件
2. 该函数处理的数据规模必须在递减
3. 这个转化必须是可解的
循环和递归
递归优点:易于理解,速度慢;存储空间大;
循环优点:不易理解,速度快,浪费空间小;
代码示例:
计算前n项和;
#include<stdio.h>
intf(int );
intmain()
{
printf("%d\n",f(100)); //f(3) n = 3 执行 f(2) f(2)调用f(1),f(1)执行printf语句;最后在栈中 //返回按顺序f(2)f(3)
return0;
}
intf(intn)
{
if (n==0)
return0;
else
{
n=n+f(n-1);
}
returnn;
}
递归计算阶乘
#include<stdio.h>
intf(int); //用来计算阶乘;
intmain()
{
inti=12;
printf("%d的阶乘为%d", i, f(i));
return0;
}
//算法:i!=i * (i - 1)!;(i-1)! = (i-1) * (i-2)!………………; 最后一个是1;可以if中返回1;或者直接返回i;结果一样;
//f(n-1);
f(inti)
{
if (i==1)
{
returni;
}
else
{
i=i*f(i-1);
returni;
}
}
函数的调用
·当在一个函数的运行期间调用另一个函数时,在运行被调用函数之前,系统需要完成三件事:
1.将所有的实际参数,返回地址等信息传递给被调用函数保存
2.为被调函数的局部变量(也包括形参)分配存储空间;
3.将控制转移到被调函数的入口
·从被调函数返回主调函数之前,系统也要完成三件事情:
1.保存被调函数的返回结果 return
2.释放被调函数所占的存储空间 free动态的内存需要手动释放
3.依照被调函数保存的返回地址将控制转移到调用函数 返回地址保存在栈中
·当有多个函数互相调用时,按照“先调用后返回(先进后出)”的原则,上述函数之间信息传递和控制转移必须借助“栈”来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作,每当一个函数退出时,就释放它的存储区,就进行出栈操作,当前运行的函数永远都在栈顶位置
·A函数调用A函数和A函数调用B函数在计算机看来是没有任何区别的,只不过用我们的日常思维比较怪异而已。
汉诺塔
伪算法:
if(n>1)
{
先把A柱子上的前n-1个盘子从A借助C移到B;
将A柱子上的第n个盘子直接移到C
再将B柱子上的n-1个盘子借助A移到C
}
代码:
//头文件
#include<stdio.h>
//函数声明
inthannuota(intn, charA, charB, charC)
{
/* 如果是一个盘子
直接将A柱子上的盘子移到C
否则:
先将A柱子上的n-1个盘子借助C移到B上
直接将A柱子上的盘子从A移到C
最后将B柱子上的n-1个盘子借助A移到C;
将n-1换成n然后A B互换;
将n n-1 n-2按顺序放到C上
*/ if (1==n)
{
printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);
}
else
{
hannuota(n-1, A, C, B);
printf("将编号为%d的盘子直接从%c柱子移到%c柱子\n", n, A, C);
hannuota(n-1, B, A, C);
}
}
intmain(void)
{
charch1='A';
charch2='B';
charch3='C';
intn;
printf("请输入要移动盘子的个数:");
scanf("%d", &n);
hannuota(n, 'A', 'B', 'C');
return0;
}
递归的应用
树和森林就是以递归的方式定义的
树和图的很多算法都是以递归来实现的
很多数学方式就是以递归方式定义的 如:斐波拉契序列 1 2 3 5 8 13 21 34
复习:
逻辑结构
线性
数组
链表
栈和队列是一种特殊的线性结构
非线性
树
图
物理结构
非线性结构
树:
树定义:
专业定义:
1.有且只有一个称为根的结点
2.有若干个互不相交的子树,这些子树本身就是一颗树;
通俗的定义:
1.树是由结点和边组成,
2.每个结点只有一个父节点,但可以有多个子节点,
3.但有一个节点例外,该节点没有父节点,此节点称为根节点
专业术语:
节点:
父节点:
子节点:
子孙:
堂兄弟:
深度:从根节点到最低层节点的层数称之为深度;根节点为第一层
叶子节点:没有字节点的节点,就是叶子节点
非终端节点:实际就是非叶子节点
度:子节点的个数就是度
分类 :
一般树:任意一个节点的个数都不受限制
二叉树:任意一个节点的子节点的个数最多两个,且子节点的位置不可更改
一般二叉树:
满二叉树:在不增加数的层数的前提下,无法再多添加一个节点的二叉树就是满二叉树;
完全二叉树:如果只是删除了满二叉树最底层最右边的连续若干个节点,这样形成的二叉树就是完全二叉树;
森林:n个互不相交的树的集合
树的存储:
二叉树的存储:
连续存储 [完全二叉树]
优点:
查找某个节点的父节点和子节点(也包括判断有没有子节点)速读很快;
缺点:
耗费内存空间大;
链式存储:(利用链表存储)
一般树的存储:
双亲表示法:
求父节点方便;
孩子表示法:
求子节点方便;
双亲孩子表示法:
求父节点和子节点都很方便;
二叉树表示法:
把一个普通树转化成二叉树来存储
具体转换方法:
设法保证任意一个节点的
左指针域指向它的第一个孩子
右指针域指向它的下一个兄弟节点;
只要能满足此条件,就可以把一个普通树转换成二叉树;
一个普通树转换成二叉树一定没有右子树
森林的存储:
先把森林转化成二叉树,再存储二叉树;
二叉树操作:
遍历
先序遍历[ 先访问根节点]
先访问根节点,再序访问左子树,再先序访问右子树
中序遍历【中间访问根节点】
中序遍历左子树
再访问根节点
再中序遍历右子树
后序遍历【最后访问根节点】
先中序遍历左子树;
再中序遍历右子树;
最后访问根节点
已知两种遍历求原始二叉树
通过先序和中序,或者中序和后序我们可以还原出原始的二叉树,但是通过先序和后序是无法还原出原始二叉树
换种说法:
只有通过先序和中序,或中序和后序
我们才可以唯一的确定一个二叉树
已知先序和中序求后序:
应用:
树是数据库中数据组织一种重要形式
操作系统子父进程的关系本身就是一颗树
面向对象语言中类的继承关系本身就是一颗树
赫夫曼树:
链式静态二叉树遍历演示代码
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<stdbool.h>
//结构体声明及定义
typedefstructBTNode
{
structBTNode* pLchild; //p指针l左chile孩子
chardate;
structBTNode* pRchild; //p指针R右chile孩子
}Tree,*PTree;
//函数声明
structBTNode*CreateBTree(void);
voidPreTraverseBTree(structBTNode*p); //先序访问
voidzhongxv(structBTNode*p); //中序访问
voidhouxv(structBTNode*p); //后续访问
//主函数
intmain(void)
{
structBTNode*pT=CreateBTree(); //创建二叉树
//PreTraverseBTree(pT);
zhongxv(pT);
return0;
}
//函数定义
structBTNode*CreateBTree(void)
{
structBTNode*PA= (structBTNode*)malloc(sizeof(structBTNode));
structBTNode*PB= (structBTNode*)malloc(sizeof(structBTNode));
structBTNode*PC= (structBTNode*)malloc(sizeof(structBTNode));
structBTNode*PD= (structBTNode*)malloc(sizeof(structBTNode));
structBTNode*PE= (structBTNode*)malloc(sizeof(structBTNode));
PA->date='A';
PB->date='B';
PC->date='C';
PD->date='D';
PE->date='E';
PA->pLchild=PB;
PA->pRchild=PC;
PB->pLchild=PB->pRchild=NULL;
PC->pLchild=PD;
PC->pRchild=NULL;
PD->pRchild=PE;
PE->pLchild=PE->pRchild=PD->pLchild=NULL;
returnPA;
}
voidPreTraverseBTree(structBTNode*pT)
{
if (pT)
{
printf("%c ", pT->date);
if (pT->pLchild)
PreTraverseBTree(pT->pLchild);
if (pT->pRchild)
PreTraverseBTree(pT->pRchild);
//伪算法:先访问根节点;再先序访问左子树;最后先序访问右子树
}
}
voidzhongxv(structBTNode*pt)
{
//伪算法,先中序访问左子树,再访问根节点,最后访问右子树;
if (pt->pLchild)
{
zhongxv(pt->pLchild);
}
printf("%c ",pt->date);
if (pt->pRchild)
{
zhongxv(pt->pRchild);
}
}
二叉搜索树
二叉搜索树:一颗二叉树,可以为空;如果不为空,满足以下性质:
1.非空左子树的所有键值小于其根节点的键值
2.非空右子树的所有键值大于其根节点的键值
3.左、右子树都是二叉搜索树。
排序和查找
折半查找
排序:
冒泡:
插入:
选择:
快速排序:
先找到一个元素的确定位置;
1.首先取出一个key,一般取第一个元素
2.从后往前遍历,如果数组中的数据小于了key,那么就将从前往后未比较过的第一个位置即fisrt位置替换为该数据
3.然后从前往后遍历,如果数组中的数据大于了key,那么就将从后往前的第一个比较过数据位置替换
4.直到左右两边的位置重合,说明key就找到了正确的位置,每次循环就能找到一个数的正确位置
5.然后将key左右两边的数据分为两组,递归调用自己。
#include<stdio.h>
voidQuickSort(int*,int ,int );
intFindPos(int*, int, int);
intmain(void)
{
inta[6] = { 2,1,0,5,4,3 };
QuickSort(a, 0, 5); //第二个参数表示第一个元素下标,第三个元素表示最后一个元素的下标
inti=0;
for (; i<=5; i++)
{
printf("%d ", a[i]);
}
return0;
}
voidQuickSort(int*a, intl, inth)
{
intpos;
if(l<h)
{
pos=FindPos(a, l, h);//找到第一个元素的位置
QuickSort(a, l, pos-1);
QuickSort(a, pos+1, h);
}
}
intFindPos(int*a, intl, inth)
{
intval=a[l];
while (l<h)
{
while(l<h&&a[h]>=val)
{
--h;
}
a[l] =a[h];
while (l<h&&a[l] <val)
{
++l;
a[h] =a[l];
}
}//终止while之后一定l = h
a[l] =val;
returnh;//h可以改为l;但不能改为val 也不能改为a[l] a[h]
}
并归排序:
查找和排序的关系:
排序是查找的前提
排序是重点
java中容器和数据结构相关知识:
iterator接口
Map:哈希表
再次讨论什么是数据结构:
数据结构研究是数据的存储和数据的操作的一门学问
数据的存储分为两部分:
个体的存储:
个体关系的存储:
从某个角度而言,数据的存储最核心的就是个体关系的存储,个体的存储可以忽略不记
再次讨论什么是泛型:
同一种逻辑结构,无论该逻辑结构物理存储是什么样子的,我们可以对它执行相同的操作;