赞
踩
1.数据结构学习方法:
心态勇于直面问题,多听多练,主动思考,多尝试
2.数据的逻辑结构
线性结构、层次结构、网状结构
3.数据结构的意义
提升自己的编程能力
可复用性高、可读性好、效率高、可维护性好
4.数据结构的基本概念
(1)数据
数据即信息的载体,是能够输入到计算机中被计算机识别、存储和处理的符号的总称
(2)数据元素
数据元素是数据的基本单位,又称为记录,数据元素由若干基本项(或称字段、域、属性)组成
5.数据的逻辑结构
集合、线性结构、树形结构、图状结构
6.存储结构
顺序存储、索引存储、链式存储、散列存储
7.线性表
包含若干元素的一个线性序列
记为:L={a0,a1,.....,ai-1,ai,.......an-1}
L为表名,其中为数据元素,n为表长,n>0时为非空表,否则为空表
用二元组形式表示为:L=(D,R)
线性表的特征:
表头无前趋,表尾无后继,其它元素有且仅有一个直接前趋和直接后继
8.顺序存储:
将线性表中的各个元素依次存储在一片连续的内存空间,称之为顺序存储
特征:
(1)逻辑上依次排列,相邻的两个元素物理位置也是相邻的
(2)对数据元素ai的存取为随机存取或按地址存取
(3)存储密度高
存储密度=数据结构中元素所占存储空间/整个数据结构所占空间
顺序存储方便查找,但也有对表的插入和删除等运算对时间的复杂度较差的缺点
#define N 100
typedef int data_t;
typedef struct
{
data_t data[N];//表的存储空间
int last;
} sqlist,*sqlink;
9.代码规范与顺序表的实现:
代码模块化编程:
sqlist.h sqlist.c test,c
优点:结构清晰明了、软件复用性好
当项目较大时,要编译的文件较多时,每一个.c文件都是要单独处理的,每一个.c文件生成的目标文件的过程是独立的。都要经过预处理、编译、汇编这三步,分别生成各自的.o目标文件,最后所有的目标文件加上库文件一起链接成可执行文件。
可以使用gcc *.c -o test将所有的.c文件生成可执行文件test
调用NULL指针要包含C库文件stdio.h
add:
Linux保存命令:
:w 保存文件但不退出vi
:w file 将修改另外保存到file中,不退出vi
:w! 强制保存,不推出vi
:wq 保存文件并退出vi
:wq! 强制保存文件,并退出vi
:q 不保存文件,退出vi
:q! 不保存文件,强制退出vi
:e! 放弃所有修改,从上次保存文件开始再编辑
函数接口:
list_create();
①申请内存 申请相应数据类型的大小的一块内存,并将malloc的返回值强制类型转换为需要的类型,接下来要判断malloc内存申请是否成功,如果失败返回NULL,即线性表指针不指向任何空间,成功返回申请后得到的内存的首地址,并且如果申请内存成功,线性表指针指向该片内存,创建的内存空间初始状态为空
②初始化 使用memset函数(要包含string.h头文件)可以从起始地址往后的一片区域插入数据,这里插入0,最后标志位last = -1,表示该线性表为空表,写程序时一定要注意这两句话的顺序,先memset再last,否则last会被再次清0
③函数返回值
返回创建好的线性表指针L
list_delete();//顺序表空间销毁
int list_delete(sqlink L) { • if(L==NULL) • { • return -1; • } • free(L); • L = NULL; • return 0; }
list_insert();
函数原型:
int list_insert(sqlink L,data_t value,int pos);
调用:list_insert(L,x,pos);
①:判断该线性表是否已满?
②:pos是否属于[0,last+1]区间 //插入规则
③:元素移动(后面元素先向后移动,依次往前)
④:存新值 last++
list_clear();//顺序表的清空
int list_clear(sqlink L) { • if(L==NULL) • { • return -1; • } • memset(L, 0, sizeof(sqlist)); • L ->last = -1; • return 0; }
list_deleteP(L,pos);//删除线性表中的元素
〇:判断是否为空表
①:检查合法性。判断要删除的位pos是否在[0,last]区间内
②:移动。被删除元素的后面紧邻元素先移动,后面元素都依次往前移动
③:更新last--
int list_deleteP(sqlink L,int pos) { • int i; • //判断表是否为空表 • if(L->last == -1) • { • printf("list is empty\n"); • return -1; • } • //pos的合法性 • if(pos<0 || pos>L->last) • { • printf("delete pos is invalid\n"); • return -1; • } • //move • for(i=pos+1;i<=L->last;i++) • { • L->data[i-1] = L->data[i]; • } • //update • L->last--; • return 0; }
C语言中不允许函数名称相同
list_purge();//去重复元素
int list_purge(sqlink L) { • int i,j; • if(L->last == 0)//单个元素不比较,直接退出 • { • return 0; • } • i=1; • while(i<=L->last) • { • j=i-1; • while(j>=0) • { • if(L->data[j] == L->data[i]) • { • list_deleteP(L,i); • break;//跳出本层循环 • } • else • { • j--; • } • } • if(j<0) • { • i++; • } • } • return 0; }
list_merge()//两个顺序表的归并
int list_merge(sqlink L1,sqlink L2) { • int i=0; • int ret; • while(i<=L2->last) • { • if((ret = list_locate(L1,L2->data[i]))==-1) • { • if((list_insert(L1,L2->data[i],L1->last+1))==-1) • { • return -1; • } • } • i++; • } • return 0; }
list_show();//顺序表的遍历
int list_show(sqlink L) { • int i; • if(L==NULL) • { • printf("can not show\n"); • return -1; • } • if(L->last == -1) • { • printf("the list is empty\n"); • } • for(i=0;i<=L->last;i++) • { • printf("%d ",L->data[i]); • } • puts(""); • return 0; }
list_empty();//判断顺序表是否为空
int list_empty(sqlink L) { • if(L->last == -1) • { • return 1; • } • else • return 0; }
list_length();//求顺序表的长度
int list_length(sqlink L) { • if(L==NULL) • { • return -1; • } • return (L->last+1); }
list_locate();//定位元素位置
int list_locate(sqlink L,data_t value) { int i=0; for(;i<=L->last;i++) { if(L->data[i] == value) { return i; } } return -1; }
顺序表的优缺点:
优点:存储密度高、便于存取数据
缺点:要求必须是一片连续的内存空间,而且进行插入、删除等操作时耗时,可能会有大片元素移动的现象。
什么时候选择使用顺序表:
当涉及到查找、更新数据较多时,使用顺序表效率较高
编程时要先画图,理清思路,再去写程序
(多画图理解)
链式存储:
将线性表L=(a0,a1,a2,....,an-1)的各元素存储在内存中的不同模块,称为结点,通过地址或指针建立元素之间的联系。
通常会引入一个头结点来指向第一个节点,方便操作,且头节点数据不重要,只要存放的是第一个结点的地址即可
结点类型描述:
typedef struct node
{
data_t data;//节点的数据域
struct node *next;//节点的后继指针域
}listnode,*linklist;
上述图片中结点放在栈上,定义好A后系统自动分配好内存空间,然后再把A结点的地址赋予结点指针P,栈上的内存有着作用域,超出作用域后内存释放
P访问成员时必须用箭头,结构体A访问成员时用“ .”
若P为NULL,访问操作成员时会出现段错误
注:一般的段错误是由于指针的非法访问或者内存越界所致
下述图片采用动态内存申请的方式申请一块内存,申请灵活,需要多少申请多少,结点放到堆上
建立单链表:
依次读入表L={a0,a1,...,an-1}中的每一个元素ai,若a[i] 不等于结束符-1,则为a[i]创建一个结点,然后插入表尾,最后返回链表的头结点指针H
链表的创建是动态形成的,算法运行前,表结构不存在
linklist list_create() { linklist H; H = (linklist)malloc(sizeof(listnode)); if(H == NULL) { printf("malloc failed\n"); return H; } H->data = 0; H->next = NULL; return H; }
空链表的表示:
一般赋初值时,将data置为0,next置空
int list_tail_insert(linklist H,data_t value) { if(H == NULL) { printf("H is NULL\n"); return -1; } //创建新节点 linklist p,q; //内存申请 if((p=(linklist)malloc(sizeof(listnode))) == NULL) { printf("malloc failed\n"); return -1; } //申请成功后赋予初值 p->data = value; p->next = NULL; //定位尾节点 q = H; while(q->next!=NULL) { q = q->next; } //尾部插入 q->next = p; return 0; }
int list_show(linklist H) { if(H == NULL) { printf("H is NULL\n"); return -1; } linklist p; p = H;//忘记赋值的话,段错误 while(p->next != NULL) { printf("%d ",p->next->data); p = p->next; } puts(""); return 0; }
基本运算的相关算法:
//链表查找
约定头结点位置为-1,往后依次是0,1,2,......
linklist list_get(linklist H,int pos) { linklist p; int i; if(H == NULL) { printf("H is NULL\n"); return NULL; } if(pos==-1) { return H; } p = H; i =-1; while(i<pos) { p = p->next; if(p == NULL) { printf("pos is invalid\n"); return NULL; } i++; } return p; }
p = list_get(H,4);
printf("value=%d\n",p->data);
上图截图出现段错误,是由于上述代码段出现p->data的情况,返回空指针,空指针又去访问数据出现段错误,非法访问。
源码:
int list_insert(linklist H,data_t value,int pos) { linklist p,q; if(H == NULL) { return -1; } //查找要插入的前一个结点,并返回其指针 p = list_get(H,pos-1); if(p == NULL) { return -1; } //新建新结点 if((q=(linklist)malloc(sizeof(listnode))) == NULL) { printf("malloc failed\n"); return -1; } //给新结点赋予初值 q->data = value; q->next = NULL; //插入 q->next = p->next; p->next = q; return 0; }
指针指向改变有先后顺序,先使 q的指针域指向ai,后使p的指针域指向新插入的元素x,即p ->next = q,否则会出现链表断掉的情况
单链表的优缺点:
优点:单链表不需要大量连续的存储空间,通过指针对元素进行操作,插入、删除时元素不需要成片移动,此时执行效率高,同时可以由用户动态申请内存,不会造成内存的浪费。
缺点:单链表查找时从头结点开始,并且只能单向移动,不能随机访问,比较耗时,效率低。
①:删除操作前需要调用链表查找接口函数list_get();,找到所要删除结点的直接前驱,假设用指针p指向该前驱结点
②:(1)定义一个临时变量q,用来存放所要删除结点的起始地址,即q = p->next ,便于后面的内存释放
(2)改变结点的指针指向,p->next = q->next
(3) 释放内存,合理利用内存,可持续发展,避免内存泄漏: free(q)
结点删除函数接口:
int list_delete(linklist H,int pos) { linklist p,q; //check if(H == NULL) { printf("H is NULL\n"); return -1; } //locate prior p = list_get(H,pos-1); //check valid? if(p == NULL) { return -1; } if(p->next == NULL)//特殊情况,删除最后一个结点的后一个结点 { printf("delete pos is invalid\n"); return -1; } //update q = p->next; p->next = q->next;//p->next = p->next->next; //free printf("delete:%d\n",q->data); free(q); q = NULL;//避免野指针! return 0; }
main函数中删除结点的实现:
int main() { linklist H; int value; H = list_create(); if(H == NULL)//链表创建失败 { return -1; } printf("input:\n"); while(1) { scanf("%d",&value); if(value == -1) { break;//结束链表创建 } list_tail_insert(H,value); printf("input\n"); } list_show(H); list_delete(H,-1);//1,3,5,7 list_show(H); return 0; }
int list_free(linklist H) { linklist p; if(H == NULL) { printf("H is NULL\n"); return -1; } p = H; printf("free:"); while(H != NULL ) { p = H; printf("%d ",p->data); H = H->next; free(p);//不能把释放内存放到前面 } puts(""); return 0; }
如果主函数中重复调用list_free()函数,则会出现下面的情况:如下分别是主函数调用和命令行显示结果:
int main() { linklist H; int value; H = list_create(); if(H == NULL)//链表创建失败 { return -1; } printf("input:\n"); while(1) { scanf("%d",&value); if(value == -1) { break;//结束链表创建 } list_tail_insert(H,value); printf("input\n"); } list_show(H); printf("H=%p\n",H); list_free(H); printf("H=%p\n",H); list_delete(H,-1); list_show(H); list_free(H); return 0; }
改进方法:
1:将list_free()函数的函数声明和定义部分的返回值改为linklist(结点指针型),list_free函数体最后返回一个空指针NULL,并在main函数中用H接收,H= list_free(H);,这样所有的内存被释放之后,又被赋予了NULL,就无法再操作内存
假如在list_free()函数最后,使用H = NULL的方式赋予空指针,这样是没用的,因为主函数中,调用list_free函数时,实参为临时变量H,形参为被调用函数的形参,改变形参的值,实参不会变
2:或者list_free传参时传二级指针,即&H,这种方法较上面的麻烦,但也行得通:
注意二级指针访问加变为一级指针访问成员,H->next,这样是错误的,会出现以下结果:(正确的做法是在H两边加上括号,因为->的优先级高于*)
error: request for member ‘next’ in something not a structure or union
思路:
代码实现:
int list_rev(linklist H) { linklist p,q; if(H == NULL) { printf("H is NULL\n"); return -1; } if(H->next == NULL ||(H->next)->next == NULL) { printf("only one\n"); return 0; } p = H->next->next; H->next->next = NULL; while(p != NULL) { q = p; p = p->next; q->next = H->next; H->next = q; } return 0; }
问题:
1.为什么不能用一个指针P来操作?
只用指针p操作时,要负责两个任务,第一遍历所有元素,其二完成插入到H后的任务,插入H后时,p的next已经被赋新值了,不能再往后遍历了,链表已经断了
2.既然形参不能改变实参,那为什么可以通过操作形参改变成员的方式去改变链表
因为传进来的参数是地址,可以通过地址访问目标结构体,址传递可以改变操控原值
3.上面的链表的反转是取出链表中的每一个节点后头部插入,改变了原有链表的结构。
那么来看一个题目:提供一个单向链表的链表首地址,实现反向打印链表的数据,要求不能改变原有链结构。
(注意要求不能改变链表的结构)
#include <stdio.h> #include <stdlib.h> typedef int data_t; typedef struct node { data_t data; struct node *next; }listnode,*linklist; linklist list_create(); int list_show(linklist H); int list_tail_insert(linklist H,data_t value); int list_rev(linklist H); int main(int argc, const char *argv[]) { linklist H; if((H = list_create()) == NULL) { return -1; } list_tail_insert(H,1); list_tail_insert(H,2); list_tail_insert(H,3); list_tail_insert(H,5); list_tail_insert(H,7); list_show(H); list_rev(H->next); puts(""); list_show(H); return 0; } linklist list_create() { linklist H; if((H = (linklist)malloc(sizeof(listnode))) == NULL) { perror("list_create-malloc"); return NULL; } H->data = 0; H->next = NULL; return H; } int list_tail_insert(linklist H,data_t value) { linklist p = NULL; if(H == NULL) { printf("list_tail_insert:H is NULL\n"); return -1; } if((p = (linklist)malloc(sizeof(listnode))) == NULL) { perror("list_tail_insert-malloc"); return -1; } p->data = value; p->next = NULL; while(H->next != NULL) { H = H->next; } H->next = p; return 0; } int list_show(linklist H) { while(H->next) { printf("%d ",H->next->data); H = H->next; } puts(""); return 0; } int list_rev(linklist H) { /* linklist p,q; p = H->next->next; H->next->next = NULL; while(p != NULL) { q = p; p = p->next; q->next = H->next; H->next = q; } */ /*或者可以这样写*/ if(H != NULL) { list_rev(H->next); printf("%d ",H->data); } return 0; }
运行结果如下:
linux@linux:~/ds/liner$ gcc test.c linux@linux:~/ds/liner$ ./a.out 1 2 3 5 7 7 5 3 2 1 1 2 3 5 7
代码思路:
递归实现在不改变链表结构的前提下,逆序打印链表数据
思路:
接口函数源码:
linklist list_adjmax(linklist H,data_t *value) { linklist p,q,r; data_t sum; if(H == NULL) { printf("H is NULL\n"); return NULL; } if(H->next == NULL || H->next->next == NULL ||H->next->next->next == NULL) { return H; } q = H->next; p = q->next; r = q; sum = q->data + p->data; while(p->next != NULL) { p = p->next; q = q->next; if(sum< q->data + p->data) { sum = q->data + p->data; r = q; } } *value = sum;//将sum的值带出函数 return r;//注意不是返回q,因为r不一定要更新 }
思路:
①:
②:
③:
退出循环体后,再判断p和q,一方为空,就把另一方直接插入到r后面
源码如下:
int list_merge(linklist H1,linklist H2) { linklist p,q,r; if(H1 == NULL || H2 == NULL) { return -1; } p = H1->next; q = H2->next; r = H1; H1->next = NULL; H2->next = NULL; while((p != NULL) && (q != NULL)) //while(p && q); { if(p->data <= q->data) { r->next = p; p = p->next; r =r->next; r->next = NULL; } else { r->next = q; q = q->next; r =r->next; r->next = NULL; } } if(p == NULL) { r->next = q; } else { r->next = p; } return 0; }
作业:无序链表的元素排序: 写一个函数,实现链表的排序(使链表中元素的从无序到有序,要求从小到大);
结果如下:
原因是:这里系统认为是定义的变量没有malloc申请内存,那么直接使用free会出错,比如:(malloc与free要配对使用)
因此free释放的应该是malloc申请的有效空间
特别注意:这里有个陷阱:明明调用函数传递参数传的是指针(节点地址),被调用函数中又使用malloc函数申请堆内存,可为什么会出错呢?
因为指针作为参数传递,其实本质是值传递,传给被调用函数后,会在栈上给形参分配空间,空间里的值就是你所传递的指针值,H1 = list_create()这句话,其实本质是malloc申请堆上的一块内存空间,强制类型转换后给形参H1赋值,形参H1被改变了,但形参不能改变实参,实参H1并未被改变,还是野指针,根本就没有接收到malloc申请的内存空间,因此free直接报错
改进:
方法1:
H1 = list_create这句话不要放被调用函数,改成放到调用函数里
方法2:
list_sort()排序函数由一个参数增加到2个参数,传递一级指针变量的地址,用二级指针变量来接收
int list_sort(linklist H,linklist *H1) { linklist p,q,r,m; if(H == NULL) { printf("list_sort:invalid operation\n"); return -1; } if(((*H1) = list_create()) == NULL) { return -1; } m = *H1; while(H->next != NULL) { r = H; p = H->next; q = p; p = p->next; while(p != NULL) { if(p->data < q->data) { q = p; while(r->next != q) { r = r->next; } p = p->next; } else { p = p->next; } } r->next = q->next; while((*H1)->next) { (*H1) = (*H1)->next; } (*H1)->next = q; printf("*H1:%d\n",(*H1)->data); q->next = NULL; } *H1 = m; /* 这种方法比上面简单多了,只需要传一个参数H linklist p,q; if(H == NULL) { printf("list_sort:invalid operation\n"); return -1; } p = H->next; q = p; while(q->next != NULL) { while(p->next != NULL) { p = p->next; if(p->data < q->data) { p->data ^= q->data; q->data ^= p->data; p->data ^= q->data; } } q = q->next; p = q; } */ return 0; }
int test_sort() { linklist H,H1; int i; data_t a[] = {9,2,4,0,5,7,1}; if((H = list_create()) == NULL) { return -1; } for(i = 0;i < sizeof(a)/sizeof(data_t);i++) { list_tail_insert(H,a[i]); } list_show(H); list_sort(H,&H1); list_show(H1); list_free(&H); list_free(&H1); return 0; }
注意测试时,list_sort函数里最后H1跑到了倒数第二个位置,实参也会跟着动,所以为了正常输出遍历结果,要使用第三方指针m来接收H1,最后再还给它
(58条消息) free(): invalid pointer的问题雪舞飞扬之殇的博客-CSDN博客free(): invalid pointer
进程退出后,malloc分配的资源会被系统回收_进程退出后占用的资源会被回收吗-CSDN博客?
数组和链表的区别:
答:
从逻辑结构上来看,数组必须实现定于固定的长度,不能适应数据动态增减的情况,即数组的大小一旦定义就不能改变。当数据增加时,可能超过原先定义的元素的个数;当数据减少时,造成内存浪费;链表动态进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。
从内存存储的角度看;数组从栈中分配空间(用new则在堆上创建),对程序员方便快速,但是自由度小;链表从堆中分配空间,自由度大但是申请管理比较麻烦。
从访问方式类看,数组在内存中是连续的存储,因此可以利用下标索引进行访问;链表是链式存储结构,在访问元素时候只能够通过线性方式由前到后顺序的访问,所以访问效率比数组要低。
栈是限制在一端进行插入和删除操作的线性表(俗称堆栈),允许进行操作的一端称为栈顶,另一固定端称为栈底,当栈中没有元素时成为空栈。 特点:后进先出
非线性问题线性化,需要用到栈,另外可以将栈看作一个容器,遵循先入后出或者后进先出,而栈和顺序表有什么不同呢?
顺序表中,元素的插入和删除可以在任意位置进行,而栈只能在栈顶进行压栈和弹出,栈的示意图如下图所示:
顺序栈定义:
定义一个结构体,取别名为sqstack(顺序栈),结构体中有三个成员,分别为:指向实际栈空间的data_t型指针data、当前栈的最大元素个数以及指示栈顶位置(数组下标)的变量top,见下图:
顺序栈的创建:
sqstack * sqstack_create();
根据定义好的顺序栈结构体sqstack,先定义一个顺序栈结构体指针s,首先申请一块内存用于存储结构体成员,s = (sqstack *)malloc(sizeof(sqstack)); 接着再申请一块内存用于存储实际栈的数据,被结构体成员data指向,即s->data = (data *)malloc(len * sizeof(data_t))
源码:
sqstack *sqstack_create(int len) { sqstack *s; if((s = (sqstack *)malloc(sizeof(sqstack))) == NULL) { printf("malloc sqstack failed\n"); return NULL; } if((s->data = (data_t *)malloc(len * sizeof(data_t))) == NULL ) { printf("malloc data failed\n"); free(s); //data指向的数据区申请失败时释放掉s s = NULL; return NULL; } memset(s->data,0,len*sizeof(data_t));//调用memset填充初值0 s->maxlen = len;//由用户定义栈的长度 s->top = -1;//将栈置空 return s; }
顺序栈的释放:
int sqstack_free(sqstack *s);
顺序栈的释放和malloc配对使用(用户申请的内存使用过后要释放掉,避免内存泄漏),下面为源码:
int sqstack_free(sqstack *s) { if(s == NULL) { printf("s is NULL\n"); return -1; } if(s->data != NULL) //判断数据存储区是否曾经创建成功 { free(s->data); } free(s); return 0; }
这里分为两种情况:
其一:
如果在创建栈时,创建data内存时失败,则要将之前malloc成功的内存(s)释放掉,防止内存泄漏(就程序而言,不写free的话,data申请内存失败后会返回一个NULL给s,这时s被置空,然而里面的成员未被释放,会造成内存泄漏)
其二:
如果整个栈创建成功的话,在上面的栈释放代码中会判断data指向的内存是否申请成功,若是,先则将这段内存释放掉,再释放掉顺序栈指针s
顺序栈的满判断
int sqstack_empty(sqstack *s);
在入栈前要先判断栈里的数据是否为满状态:(top)
可以将栈视为一个装水的容器,上面带有刻度,而top就像是浮标,数据入栈时从栈顶入栈,数据开始从栈底堆叠,水涨船高,top浮标也跟着上浮,当top到达maxlen-1时,数据存储区已满
int sqstack_full(sqstack *s) { if(s == NULL) { printf("s is NULL\n"); return -1; } return (s->top == s->maxlen-1 ? 1 : 0);//采用三目运算符,当top指向了maxlen-1,视为满 }
顺序栈的空判断
当s->top等于-1时视为无元素,此时栈为空
源码:
int sqstack_empty(sqstack *s) { if(s == NULL) { printf("s is NULL\n"); return -1; } return (s->top == -1 ? 1 : 0); }
顺序栈的入栈
int sqstack_push(sqstack *s,data_t value);
int sqstack_push(sqstack *s,data_t value) { if(s == NULL) { printf("s is NULL\n"); return -1; } if(s->top == s->maxlen-1) { printf("sqstack is full\n"); return -1; } s->top++; s->data[s->top] = value;// *(s->data + s->top) = value; return 0; }
先将top加1,再将top指向的数据赋值,入栈时先要判断栈是否为满状态
顺序栈的出栈:
data_t sqstack_pop(sqstack *s);
data_t sqstack_pop(sqstack *s) { if(s == NULL) { printf("s is NULL\n"); return -1; } s->top--; return (s->data[s->top+1]); }
出栈时要先判断栈是否处于空状态,下面为在main函数中的调用,需要在main函数里实现
while(!sqstack_empty(s)) //出栈前先判断栈是否为空,需要在main函数里写,由用户判断
{
printf("pop:%d\n",sqstack_pop(s));
}
需要循环调用该接口函数,如果在接口函数里实现的话,while(1)是死循环,跳不出来,命令行会一直显示返回值
链式表中头结点可以看作是栈的top指针,但是只在栈顶位置做插入删除,top不会变化
结构体定义体:
typedef int data_t; typedef struct node { data_t data; struct node *next; }listnode,*linkstack;
①:链式栈的创建
申请内存,创建一个空链表s,并作初始化,代码如下:
linkstack stack_create() { linkstack s; s = (linkstack)malloc(sizeof(listnode)); if(s == NULL) { printf("s is NULL\n"); return NULL; } s->data = 0; s->next = NULL; return s; }
②:压栈
即在头节点s后尾部插入
int stack_push(linkstack s,data_t value) { linkstack p; if(s == NULL) { printf("s is NULL\n"); return -1; } p = (linkstack)malloc(sizeof(listnode)); if(p == NULL) { printf("p is NULL\n"); return -1; } p->data = value; //p->next = NULL; p->next = s->next; s->next = p; return 0; }
③:出栈
即头节点后栈顶元素释放
data_t stack_pop(linkstack s) { linkstack p; data_t t; p = s->next; s->next = p->next; t = p->data; free(p); p = NULL; return t; }
示意图:
④:链式栈释放
linkstack stack_free(linkstack s) { linkstack p; if(s == NULL) { printf("s is NULL\n"); return NULL; } while(s != NULL) { p = s; s = s->next; printf("free:%d\n",p->data); free(p); } return NULL; }
示意图:
⑤:判断空状态?
int stack_empty(linkstack s) { if(s == NULL) { printf("s is NULL\n"); return -1; } return (s->next == NULL ? 1 : 0);//返回三目运算符结果 }
⑥:查看栈顶元素
data_t stack_top(linkstack s) { return (s->next->data); }
队列:
队列是限制在两端进行插入删除操作的线性表
允许进行存入一端称为“队尾”,允许进行删除一端称为“队头”;线性表没有元素时成为空队
特点:先进先出
单向队列一端进一端出,双端队列可以两端分别进出元素
结构体定义:
typedef int data_t; #define N 100 typedef struct { data_t data[N]; int front; int rear; }sequeue;
为什么要使用循环队列?
其一是为了充分利用队列内存空间。当进行队列操作时,队尾指针向后移动,数据元素入队,此时如果队头指针也向后移动时,数据元素出队,就导致出队的位置被空了下来,循环队列可以循环存取数据元素,内存利用率高。
其二,不使用循环队列时,当进行频繁的队列操作时,队尾指针频繁后移,会发生越界,是不被允许的,当使用循环队列时,对数据元素的存取一直在一定范围的内存空间内进行。
设以数组A[m]存放循环队列的元素,其头尾指针分别为front和rear,则当前队列中的元素个数为( )。
A)==(rear-front+m)%m== B)rear-front+1
C)(front-rear+m)%m D)(rear-front)%m
正确答案:A
①:循环队列的创建
先malloc一个内存空间,强制类型转化为顺序队列指针并被sq接收
初始化:调用memset函数,在申请的内存区间内全部用0填充,最后将队列置空,或者用bzero函数
sequeue *queue_create() { sequeue *sq; if((sq = (sequeue *)malloc(sizeof(sequeue))) ==NULL) { printf("malloc failed\n"); return NULL; } memset(sq->data,0,sizeof(sq->data)); sq->front = 0; sq->rear = 0; return sq; }
②:队列的空状态判断:
front与rear指针相等即为空
int queue_empty(sequeue *sq) { if(sq == NULL) { return -1; } return (sq->front == sq->rear ? 1 :0); }
③:队列的满状态判断:
见上图,若(sq->rear +1) %N == sq->front时,即为满状态,循环队列中不是简单的rear堆叠的问题,而是要使rear+1对N取余,才能构成一个循环回路
int queue_full(sequeue *sq) { if(sq == NULL) { return -1; } if(((sq->rear +1) % N) == (sq->front)) { printf("enqueue is full\n"); return 1; } else { return 0; } }
④:队列的入队
要先判断队列是否处于满状态,再做赋值处理
int enqueue(sequeue *sq,data_t x) { if(sq == NULL) { return -1; } if(((sq->rear +1) % N) == (sq->front)) { printf("enqueue is full\n"); return -1; } sq->data[sq->rear] = x; sq->rear = (sq->rear +1) % N;//新的rear值加1对N取余后赋给旧rear,这样就可保证rear在[0.5]区间内 return 0; }
红色代码为先存值,再更新rear
⑤:队列的出队
要先用一个第三变量存储先前的值,再对front进行加一取余更新,最后返回第三变量
data_t dequeue(sequeue *sq) { data_t t; if(sq == NULL) { return -1; } t = sq->data[sq->front]; sq->front= (sq->front +1) % N; return t; //return (sq->data[(sq->front+N - 1) % N]); } 注意将代码改为红色部分更为简单
⑥:释放
sequeue *queue_free(sequeue *sq) { if(sq == NULL) { return NULL; } free(sq); sq = NULL; return NULL; }
队列元素的插入在队尾操作,删除在队头操作,由队尾指针和队头指针分别控制
首先应该定义两个结构体:(结点结构体和链式队列结构体)
typedef int data_t;
typedef struct node
{
data_t data;
struct node *next;
}listnode,*linklist;
typedef struct
{
linklist front;
linklist rear;
}linkqueue;
①:链式队列的创建;
见上图,定义一个lq指针用于接收创建好的链式队列结构体的地址,调用malloc函数申请一段内存,lq指向该内存空间,接着再申请一段内存,将该内存的首地址返回给front,同时front赋给rear,判断是否创建内存空间成功,之后给头结点赋初值。
源码:
linkqueue *queue_create() { linkqueue *lq; /* linklist front,rear; if((front = (linklist)malloc(sizeof(listnode))) == NULL) { printf("malloc listnode failed\n"); return NULL; } if((lq = (linkqueue *)malloc(sizeof(linkqueue))) == NULL) { printf("malloc linkqueue failed\n"); return NULL; } lq->rear = lq->front; lq->front->data = 0; lq->front->next = NULL; */ if((lq = (linkqueue *)malloc(sizeof(linkqueue))) == NULL) { printf("malloc linkqueue failed\n"); return NULL; } if((lq->rear = lq->front = (linklist)malloc(sizeof(listnode))) == NULL) { printf("malloc listnode failed\n"); return NULL; } lq->front->data = 0; lq->front->next = NULL; return lq; }
②:入队
首先创建一个新节点,p为结点指针,然后将p的地址赋给rear的指针域,然后将rear更新,源码如下:
int enqueue(linkqueue *lq,data_t x) { linklist p; if(lq == NULL) { printf("lq is NULL\n"); return -1; } if((p = (linklist)malloc(sizeof(listnode))) == NULL) { printf("p is NULL\n"); return -1; } p->data = x; p->next = NULL; lq->rear->next = p; lq->rear = lq->rear->next; return 0; }
③:出队
首先定义一个结点指针p,将front的地址先暂时存放,然后将front更新,同时释放掉p指向的内存空间,同时将p置空,最后返回释放掉的数据。
注:这里采用删除头结点的方式,另外一种方法是删除队头队尾中间的结点,只不过剩最后一个尾部结点时,需要再将rear回调,使rear和front相等。
源码:
data_t dequeue(linkqueue *lq) { linklist p; if(lq == NULL) { printf("lq is NULL\n"); return -1; } p = lq->front; lq->front = p->next; free(p); p = NULL; return (lq->front->data); }
④:判断链式队列是否为空(不用判断满状态,因为malloc进行动态申请内存)
空状态判断依据是:两个链式指针相等,或者front的指针域为NULL
int queue_empty(linkqueue *lq) { if(lq == NULL) { printf("lq is NULL\n"); return -1; } return (lq->front == lq->rear ? 1 : 0);//或者返回一个lq->front->next == NULL; }
⑤:清空队列
保留头结点
int queue_clear(linkqueue *lq) { linklist p; if(lq == NULL) { printf("lq is NULL\n"); return -1; } while(lq->front->next != NULL) { p = lq->front; lq->front = p->next; printf("clear-free:%d\n",p->data); free(p); p = NULL; } return 0; }
⑥:释放队列
全部释放
注意判断条件与置空
linkqueue * queue_free(linkqueue *lq) { linklist p; if(lq == NULL) { printf("lq is NULL\n"); return NULL; } while(lq->front) { p = lq->front; lq->front = p->next; printf("free:%d\n",p->data); free(p); p = NULL; } free(lq); lq = NULL; return lq; }
假溢出:
什么是假溢出呢,首先我们先了解下什么是队列中的溢出/上溢,也就是当队列里的空间已经满了,还继续往队列中插入元素,就会使队列的基础数组越界而导致代码破坏;而假溢出则是由于头尾指针都不断地向前移因而超出向量空间,这时整个向量空间及队列是有空的地方却产生了"上溢"现象。
就像上图中的0和1 位置明明是空的,末端指针却指在数组外面因此加不进新的元素,就产生了数组越界的错误。
因此我们采用循环队列来防止假溢出的现象的发生。
问题描述:
原理:
要解决的问题:
示意图如下:
分析:
三个时间指示器可看作三个栈容器,并且都是有最大存储容量的容器,因此考虑到顺序栈(maxlen),入栈出栈元素满足先进后出,球队列可以选择用更为方便的链式队列。
根据题意,三个栈容器均为空时,时间指示为0:0,当三个栈容器分别达到上限时,时间指示为11:59,最后再放一个球,时间归零,构成一个循环。故总的球链式队列里初始值应为27个球。
源码如下:
int check(linkqueue *lq); int main() { linkqueue *lq; sqstack *s_hour,*s_five,*s_min; //定义三个顺序栈 int i,min = 0; //定义初始时间 int value; if((lq = queue_create()) == NULL) { return -1; } for(i=1;i<=27;i++) { enqueue(lq,i); //循环入队,设置初始元素队列 } if((s_hour = sqstack_create(11)) == NULL) //创建11容量的一小时栈 { return -1; } if((s_five = sqstack_create(11)) == NULL) //创建11容量的五分钟栈 { return -1; } if((s_min = sqstack_create(4)) == NULL) //创建4容量的一分钟栈 { return -1; } while(1) { min++; //循环计时,每过一分钟就从队列中取出一个球 if(!queue_empty(lq)) { value = dequeue(lq); //非空取球 if(!sqstack_full(s_min)) //未满元素压栈,即分钟指示器多一个球 { sqstack_push(s_min,value); } else { while(!sqstack_empty(s_min)) //如果分钟指示器满了,再放入一个球,分钟指示器的4个球会出栈入队(与入栈顺序相反放入队尾),直到栈为空 { enqueue(lq,sqstack_pop(s_min)); } if(!sqstack_full(s_five)) //如果这时5分钟指示器未满,则压栈 { sqstack_push(s_five,value); } else { while(!sqstack_empty(s_five))//否则,视为5分钟指示器也满了,这时将5分钟指示器元素出栈入队,直到为空 { enqueue(lq,sqstack_pop(s_five)); } if(!sqstack_full(s_hour)) //判断小时指示器是否已满,未满则将这个球放入小时指示器 { sqstack_push(s_hour,value); } else { while(!sqstack_empty(s_hour)) //否则,也即是小时指示器也满了,则出栈入队,直到栈为空 { enqueue(lq,sqstack_pop(s_hour)); } enqueue(lq,value); //将最后未放入小时指示器的球置入队尾,此时达到一个循环 //0:0 if(check(lq)) //检查链式队列是否升序 { break; //若升序,则跳出整个循环,计时结束 } } } } } } printf("total:min=%d\n",min); printf("dequeue:"); while(!queue_empty(lq)) { printf("%d ",dequeue(lq)); } puts(""); return 0; } int check(linkqueue *lq)//判断是否升序,前一个数与后一个数比较,若小于继续比较,否则退出,直到比完 { linklist p; if(lq == NULL) { return -1; } p = lq->front->next; while(p && p->next) { if(p->data < p->next->data) { p = p->next; } else { return 0; } } return 1; }
树的概念:
有序树和森林:
树的逻辑结构:
二叉树:
概念:
二叉树的性质:
性质一:二叉树第i层上的节点最多为2的(i-1)次方个(i>=1)
性质二:深度为k的二叉树节点数最多有2的k次方减一(k>=1)
性质三:具有n个节点的完全二叉树深度为:(log2n)+1 或者log2(n+1)
另外二叉树满足:度数为0的节点数等于度数为2的节点数加1
满二叉树:
深度为k时有2的k次方减一个节点
完全二叉树:
只有最下面两层度数小于2,且最下面一层的节点集中在最左边的若干位置上
完全二叉树的度数为1的结点只有两种情况,0或1
二叉树的顺序存储:
可以利用数组进行顺序存储,数组元素下标与二叉树的节点编号一一对应,下标0元素不用,但是这种有时会浪费存储空间,因为当遇到不完全二叉树时,需要添加虚节点,此时虚节点会占据存储空间
因此采用二叉树的链式存储结构:
结构体定义:
二叉树的遍历:
遍历:
为什么说二叉树是非线性的呢?
学习线性表时,知道线性表是表头无前驱,表尾无后继,其他节点有且仅有一个直接前驱和一个直接后继,像一串糖葫芦,是一串线性元素的序列,而对于二叉树,根节点后有多个后继节点时,这时就是树形结构,是一种非线性结构。
因为每一个节点有两个后继结点,因此存在着如何遍历的问题,每一种遍历的搜索路径不一样,遍历的结果也不一样,分情况进行遍历:
以先序遍历为例:
创建:
二叉树中的所有的字母均由用户输入(包括#号),#号代表接口函数的出口,返回NULL,图中所有的空操作全部用#号代替。
二叉树创建源码如下:
bitree *tree_create() { data_t ch; bitree *r; scanf("%c",&ch); if(ch != '#') { if((r = (bitree *)malloc(sizeof(bitree))) == NULL) { printf("malloc failed\n"); return NULL; } r->data = ch; r->left = tree_create(); r->right = tree_create(); } else { return NULL; } return r; }
其中,先定义一个二叉树根节点指针,用来接收创建的二叉树结点内存空间返回的首地址(强制类型转换),创建好之后,给该节点赋予初值:data域和左右子树,左右子树分别再次递归调用二叉树创建函数,执行创建函数过程中遇到#跳出,最后返回根节点指针r
二叉树先序遍历源码:
每次递归执行时执行够花括号里的四句话,遇到出口条件逐层跳出,直至结束
int preorder(bitree *r) //二叉树的根节点,看作先序遍历起始的地方 { if(r == NULL) { return 0; } printf("%c",r->data); preorder(r->left); //递归调用先序遍历函数,遇到#跳出一层循环 preorder(r->right); return 0; } 类似,中序和后序的源码如下: int inorder(bitree *r) { if(r == NULL) { return 0; } inorder(r->left); printf("%c",r->data); inorder(r->right); return 0; } int postorder(bitree *r) { if(r == NULL) { return 0; } postorder(r->left); postorder(r->right); printf("%c",r->data); return 0; }
三种遍历示意图;
先序遍历算法剖析:(想不明白时,最好画出最简单的二叉树,将其按照递归算法捋一捋)
层次遍历:
声明一个队列,用于存放父节点,便于之后找左右子树,基本思路是:
访问打印根节点的数据,并使根节点入队。下面构建一个循环,结束条件是队列为空,然后将根节点出队,判断根节点的左子树是否为空,若不为空则访问左子树,访问完后将该左子树入队;若为空,再判断有无右子树,若右子树不为空,则访问右子树的数据,并将右子树节点掷入队列,接下来是又一层执行。队列不为空,将先前掷入队列的节点出队访问,判断有无左右子树...............
以上面的二叉树遍历图为例:先访问根节点A,再将A入队,接着进入循环语句,队列不为空时将A出队,判断左右子树,访问左右子树的数据,然后将左、右子树入队.........
根据以上的思路,则层次遍历的顺序为A-B-E-C-F-D-G-H-K
源码如下:
int layerorder(bitree *r) { linkqueue *lq; if((lq = queue_create()) == NULL) { return -1; } if(r == NULL) { return 0; } printf("%c",r->data); enqueue(lq,r); while(!queue_empty(lq)) { r = dequeue(lq); if(r->left) { printf("%c",r->left->data); enqueue(lq,r->left); } if(r->right) { printf("%c",r->right->data); enqueue(lq,r->right); } } puts(""); return 0; }
曾出现这样的问题:矛盾类型、重复定义
文件包含情况如下:
观察可发现,bitree.h与linkqueue.h头文件均被包含在bitree.c文件中,另外我在这两个头文件中均加上了条件编译,可为什么还会报这样的错误呢?
细看可发现,bitree.h与linkqueue.h头文件中定义的类型有一样的、重复的(比如:data_t,node)这样的话,预处理阶段进行头文件展开生成.i文件,会将bitree.h先展开,再将linkqueue.h展开,而linkqueue.h文件中又包含了bitree.h,于是又将bitree.h在bitree.c文件中展开,最后将linkqueue.h中剩下内容展开,生成最终的.i文件。
于是,bitree.h包含了两次,linkqueue.h包含了一次,此外bitree.h与linkqueue.h都进行了条件编译,因此编译时只将bitree.h与linkqueue.h各编译一次。但是重点是,即使这样,bitree.h与linkqueue.h因有重复内容,编译时也会出错
现在改为如下:将bitree.h文件中的data_t、node改为datatype、node_t
再次编译:正常
此外将bitree.c中的bitree.h注释掉也可以,因为bitree.h已经包含在了linkqueue.h,这样包含了linkqueue.h的情况下,会将bitree.h与linkqueue.h各包含一次,编译时也均编译一次
但是注意:如果将linkqueue.h文件中的#include "bitree.h"注释掉,编译时会出现以下错误:
虽然看上去和上面的类似,也都各包含一次,但是由于没有包含到linkqueue.h文件中,就会出现未知类型、找不到相关类型的错误
查找的概念:
查找的方法:
自己去实现顺序表查找与折半查找
查找的平均长度:
哈希查找法:
哈希查找可以不经过比较就可以查找到相应的记录,其时间复杂度为常数级O(C),需要建立记录与地址的链接关系
哈希函数(散列函数、杂凑函数):
通常要找Key = k的记录时,其中k为用户要查找的数,通过建立哈希函数确定记录所在的地址,要查找时,先定位到地址,再进行查找访问,H(key)记为哈希函数,自变量key是记录,函数值为存储地址或哈希地址。
冲突:
对于同一个哈希函数,不同的key可能对应同一个地址,这时称两个记录为同义词,该现象为“冲突”
事实上,选取好的哈希函数只能尽量使冲突减少,却不能完全避免冲突现象的发生,这就需要考虑到冲突数据的存放问题
哈希表:
将一组记录(线性表),通过某种映射关系(哈希函数与解决冲突方法)映射存放到一个存储空间中,形成一个记录表,该记录表称为哈希表
哈希表的查找:
哈希函数的构造有多种方法:直接地址法、平方取中法、叠加法、保留余数法、随机函数法,主要介绍最常用的保留余数法
保留余数法:
H(key)= key % p ,p为不大于哈希表长m的最大质数
p选取质数比合数的哈希函数的随机度要好,散列程度要优,可以使得冲突现象减少
使用随机度好的哈希函数可以使得冲突减少,但不能完全避免冲突现象的发生,也就是说冲突现象可能再哈希表中发生,例如根据哈希函数得到哈希地址,不同的记录可能对应多个哈希地址,一个地址内存只能存放一个记录,这就导致了冲突现象的发生
解决冲突的方法:
哈希表的装填因子一般选取经验值0.7-0.8之间,根据记录数推算出表长m,视为哈希表长,另外表中留有余量,减少装填数据造成的冲突和聚积
开放地址法:
示意图:
链地址法:
有这样一组记录:key={23,34,14,38,46,16,68,15,7,31,26},共11个记录数据,根据前面的内容,装填因子 = n/m,选取装填因子为0.75,可推算出m=15,根据哈希函数H(key) = key % p,选取最大质数为13,即哈希表长为15,余数空间为【0,12】,也就是说记录如果分散到哈希表中,数据以链式存储的方式分布于哈希地址为【0,12】的空间。
示意图:
结构体定义:
首先定义一个链式节点listnode,成员分别为key、value、next,再将其扩充封装为一个链式节点数组,定义为hash,数组中每一个元素都是一个链式节点
#ifndef __HASH_H__ #define __HASH_H__ #define N 13 typedef int datatype; typedef struct node { datatype key; datatype value; struct node *next; }listnode,*linklist; typedef struct { listnode data[N]; }hash; hash *hash_create(); int hash_insert(hash *HT,datatype key); linklist hash_search(hash *HT,datatype key); #endif
然后定义一个hash指针HT(hash *HT)指向这一片链式节点结构体数组,便可用HT指针进行记录结点的插入和查找操作,类似于链表中的头指针
创建:
hash *hash_create() { hash *HT; if((HT = (hash *)malloc(sizeof(hash))) == NULL) { printf("malloc failed\n"); return NULL; } memset(HT,0,sizeof(hash)); return HT; }
定义一个HT指针,用于接收申请内存返回的首地址,若申请失败返回NULL,成功进行初始化赋值,调用memset函数填充
插入:(有序插入)
int hash_insert(hash *HT,datatype key) { linklist p,q; if(HT == NULL) { return -1; } if((p = (linklist)malloc(sizeof(listnode))) == NULL) { printf("malloc listnode failed\n "); return -1; } p->key = key; p->value = (key % N); p->next = NULL; q = &(HT->data[key % N]); while(q->next && q->next->key < p->key) //退出条件为q的指针域为空,此时在后面追加 ,另外如果q指向的下一个结点的记录不小于要插入的记录,那就退出循环,在两节点中间插入 { q = q->next; } p->next = q->next; q->next = p; return 0; }
先定义一个链式指针p,用于接收申请内存后返回的首地址,p指向该内存空间,接着对p所指向的内存,进行初始化赋值,其中key为记录,value为保留余数法求出的哈希地址,next为指向下一节点的指针域。
初始化赋值后选择要插入的那一串链表,即要先找到其头结点,这里用 q = &(HT->data[key % N]),以哈希地址为下标的数组元素就是对应头结点,然后加上7取地址,赋给指针变量q,q为该串链表的头结点指针,然后进行插入操作(循环出口为q指针域为空或要插入的值不大于q指针的下一结点的记录)
查找:
linklist hash_search(hash *HT,datatype key) { linklist p; if(HT == NULL) { return NULL; } p = &(HT->data[key % N]); while(p->next && p->next->key != key) { p = p->next; } if(p->next == NULL) { printf("not found:\n"); return NULL; } else { printf("found:\n"); return p->next; } }
类似于插入操作,先找到所在链表的头结点,如果p的指针域为空或者p指向的下一个节点的记录等于用户要查找的值,接着进行判断输出对应的结果
排序定义:
内排序方法:
而插入排序又可分为:
(1)直接插入排序:
(2)折半插入排序:
(3)链表插入排序:
①:冒泡排序(自己去实现,一定要熟练,本节不予实现)
②:快速排序:(非常重要,一定要熟练理解)
快排基本思路:(递归)
给一个数组或者记录集合,先找一个基准值(第一个元素),并且用一第三变量存起来。默认基准值右边的数据比基准值大、左边的数据比基准值小,以此为规则进行比较,从右边开始比较,基准值右边比基准值大的数不用动,high--(记录两个游标low、high),若遇到基准值右边比基准值小的数,则将该数放到基准值处,方便理解可以认为基准值虚假占位(实际上基准值已经被存放到第三变量中,不用进行交换,此后开始正序比较,遇到左边比基准值大的数,数据送到刚刚反序截止的high位,该位置被基准值虚假占据),以此类推,第一次执行到退出条件时,找到一个中间轴,数据被分为两部分,分成的两部分再次调用快排函数(递归),反复调用执行,直到结束
#include <stdio.h> #include <stdlib.h> #define N 15 //int compare(const void *p1,const void *p2); int partion(int *data,int low,int high); int quicksort(int *data,int low,int high); int main() { int data[N] = {0}; int i; srandom(9); //随机值函数 for(i=0;i<N;i++) { data[i] = random() % 100; } for(i=0;i<N;i++) { printf("%d ",data[i]); } puts(""); quicksort(data,0,N-1); //qsort(data,N,sizeof(int),compare);//库函数快排 for(i=0;i<N;i++) { printf("%d ",data[i]); } puts(""); return 0; } int partion(int *data,int low,int high) { int tem = data[low]; while(low < high) { while(low < high && tem <= data[high]) //如果碰到极端情况基准值右边的数都比基准值大,循环会被一直执行,即使low和high相等high还在减,大括号不能对其控制,故应把大括号的退出条件加到内层循环中 { high--; } data[low] = data[high]; while(low < high && tem >= data[low]) //同上 { low++; } data[high] = data[low]; } data[low] = tem; return low; } int quicksort(int *data,int low,int high) { int t; if(data == NULL) { return -1; } if(low >= high) //为什么这里写为 low == high 同目录下的作业文件quicksort1c会报段错误 { return 0; } t = partion(data,low,high); quicksort(data,low,t-1); quicksort(data,t+1,high); return 0; } /*int compare(const void *p1,const void *p2) { return (*(const int *)p1 - *(const int *)p2); }*/
如果low == high:
、
具体见错题集
使用库函数快排:
多层循环编程经验:
做一件事一般要分成两步,第一步先确定分几步进行,为最外层循环,第二步确定每一大步里面的每一小步该怎么实现,为内层循环
①:冒泡排序:
#include <stdio.h> int main() { int i,j; int t,n; int data[] = {1,5,3,8,6,2,0}; n = sizeof(data)/sizeof(int); for(i=0;i<n;i++) { printf("%d ",data[i]); } puts(""); for(i=0;i<n-1;i++) { int len = n; for(j=0;j<len-1;j++) { if(data[j]>data[j+1]) { t = data[j]; data[j] = data[j+1]; data[j+1] = t; } } len--; } for(i=0;i<n;i++) { printf("%d ",data[i]); } puts(""); return 0; }
②:简单插入排序:
思路:
例如对于一组无序数据{3,2,5,4,11,15,13},第0个数据为基准值下标为end,从第1个数据开始,先用第三变量存储,再和前一个数据进行比较,如果比前一个数据小,那就end--,直到碰到比该数据大的跳出循环,将先前保存的值插入到该位置的后一个位置,如果所比较的所有数都比该数据大,那么当end=-1时直接结束循环,该数据直接赋予a[0]
#include <stdio.h> int main() { int a[] = {3,2,5,4,11,15,13}; int i,end; int n,t; n = sizeof(a)/sizeof(int); for(i=0;i<n-1;i++) { end = i; t = a[end+1]; while(end >= 0) { if(t < a[end]) { a[end+1] = a[end]; end--; } else { break; } } a[end+1] = t; } for(i=0;i<n;i++) { printf("%d ",a[i]); } puts(""); return 0; }
③:希尔排序:
思路:
例如对于一个数组{9,1,2,5,7,4,8,6,3,5},希尔排序的思想为将数组先分成若干组,对每一组进行直接插入排序(增量为d1,程序里为gap=n/2),然后对新数组再分为若干组,在对每一组进行直接插入排序(增量为d2<d1),以此类推,直到dt=1时结束排序,而且发现dt=1即为简单插入排序
下面取gap为每一循环增量减半:
#include <stdio.h> int main() { int a[] = {9,1,2,5,7,4,8,6,3,5}; int i,end,gap; int n,t; n = sizeof(a)/sizeof(int); gap = n/2; while(gap >= 1) { for(i=0;i<n-gap;i++) { end = i; while(end >= i) { t = a[end + gap]; if(t < a[end]) { a[end + gap] = a[end]; end--; } else { break; } } if(end < i) { a[end + 1] = t; } } gap /= 2; } for(i=0;i<n;i++) { printf("%d ",a[i]); } puts(""); return 0; }
④:选择排序:
思路:
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
选择排序的思想其实和冒泡排序有点类似,都是在一次排序后把最小的元素放到最前面,或者将最大值放在最后面。但是过程不同,冒泡排序是通过相邻的比较和交换。而选择排序是通过对整体的选择,每一趟从前往后查找出无序区最小值,将最小值交换至无序区最前面的位置。
#include <stdio.h> int main(int argc,char *argv[]) { int a[10] = {9,1,2,5,7,4,8,6,3,5}; int b[10] = {0}; int i=0,j,r=1; int min,t; for(j=0;j<10;j++) { r = i; while(r<=9) { if(a[i] > a[r]) { t = a[i]; a[i] = a[r]; a[r] = t; min = a[i]; } r++; } b[j] = min; i++; } for(j=0;j<10;j++) { printf("%d ",b[j]); } puts(""); return 0; }
⑤:归并排序:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。