750 likes | 895 Views
程序设计技术. 程序设计语言和算法描述 C 程序设计入门 C 程序的控制结构 函数与程序结构 指针与函数 构造数据类型与指针 位运算 文件 C 语言应用. C 语言应用. 线性数据结构及其处理技术 常用的排序技术 常用查找技术 图形程序设计基础. 线性数据结构及其处理技术. C 语言中的自引用结构 C 语言规定,在定义结构体构造数据类型的时候,结构体类型中的数据成员可以是该结构体类型自己的指针类对象(包括指针变量和指针数组)。这种在一个结构体类型定义中包含有该结构体类型指针类对象的结构体称为自引用结构。 struct test
E N D
程序设计技术 • 程序设计语言和算法描述 • C程序设计入门 • C程序的控制结构 • 函数与程序结构 • 指针与函数 • 构造数据类型与指针 • 位运算 • 文件 • C语言应用
C语言应用 • 线性数据结构及其处理技术 • 常用的排序技术 • 常用查找技术 • 图形程序设计基础
线性数据结构及其处理技术 • C语言中的自引用结构 • C语言规定,在定义结构体构造数据类型的时候,结构体类型中的数据成员可以是该结构体类型自己的指针类对象(包括指针变量和指针数组)。这种在一个结构体类型定义中包含有该结构体类型指针类对象的结构体称为自引用结构。 struct test { char ch; struct test *next; }; 式中: next是指向结构体类型struct test的指针变量
线性数据结构及其处理技术 • 变形的自引用结构 struct A { int x; struct B *pb; }; struct B { int y; struct A *pa; }; 例9-1变形的自引用结构体。 • 错误的自引用结构 struct test { char ch; struct test next; }; 错误原因: next结构体类型struct test的一般变量
线性数据结构及其处理技术 • C语言中的动态存储分配函数 • 存储分配函数malloc 原型:void *malloc(unsigned int size); 功能:在主存储器中分配由size所指定大小的存储块,返回所分配存储块在存储器中起始位置(指针)。返回指针类型为void(空类型),在应用程序中应根据需要进行相应的类型转换。如果存储器中没有足够的空间分配,即当存储分配失败时返回NULL。 • 存储释放函数free 原型:void free(void *ptr); 功能:释放有指针变量ptr指明首地址的存储块,即将该块归还操作系统。 例9-2从键盘上输入若干字符串行,然后将其输出到屏幕上。要求输入字符串的行数在程序运行过程中指定。
线性数据结构及其处理技术 • C语言中的动态存储分配函数 • 重新分配存储函数realloc 原型:void *realloc(void *ptr, unsigned int newsize); 功能:将以ptr值为首地址的存储块长度调整为newsize所指定的长度。分配成功时,若返回的新存储块首地址与由ptr确定的原首地址相同,则不需要进行原块内存放内容的拷贝;若返回的新存储块的首地址与ptr确定的原首地址不同,则需要将原块内存放内容的拷贝新分配的存储块中。 例9-3在字符串的连接处理过程中有可能出现原块长度不够的情况,此时需要对原存储块调整为所需要的长度。
线性数据结构及其处理技术 • 线性表的存储结构及基本运算 • 数据的逻辑结构 数据组织形式从逻辑上抽象地反映了数据元素之间的结构关系,则称这种数据之间的结构关系为数据的逻辑结构。数据的逻辑结构包含两个大类: • 线性结构 • 非线性结构 • 线性表 线性表中,各数据元素之间的逻辑结构可以用一个简单的线性结构表示出来,其特征是:除第一个和最后一个元素外,任何一个元素都只有一个直接前趋和一个直接后继;第一个元素无前趋而只有一个直接后继;最后一个元素无后继而只有一个直接前趋。
线性数据结构及其处理技术 • 线性表的存储结构及基本运算 • 线性表上常用的运算 • 查找:查找表中满足某种条件的数据元素; • 取数:取得表中某个数据元素; • 插入:在表中的指定位置插入一个新的数据元素; • 删除:删除表中某个指定的数据元素; • 求长度:计算线性表的长度; • 排序:按要求重新排列线性表中的数据元素; • 合并:按要求将多个线性表合并成为一个线性表; • 拆分:按要求将一个线性表拆分成为多个线性表; • 复制:按要求从一个线性表拷贝生成另外一个新的线性表;
LOC(0) 0号元素 LOC(1)=LOC(0)+M 1号元素 … LOC(i)=LOC(0)+i*M i号元素 … LOC(n-1)=LOC(0)+(n-1)*M n-1号元素 线性数据结构及其处理技术 • 线性表的存储结构及基本运算 • 数据的逻辑结构在计算机存储设备中的映像(具体存储形式)成为数据的存储结构,亦成为数据的物理结构。在对线性表的处理中,其主要的存储结构有顺序存储结构和链式存储结构。 • 顺序表—线性表的顺序存储结构 线性表的顺序存储结构是把逻辑上相邻的数据元素存储在物理上相邻的存储单元中。这种存储方式的优点是存储密度大,存储空间的利用率高;其缺点是能够容纳的数据元素个数有限制,而其当需要进行数据元素的插入或删除操作时需要移动大量的数据元素。 在C语言中用数组来表示线性表的顺序存储结构,称为顺序表。顺序表只有数据域,没有指针域. 数据元素在存储器中存放起始位置的计算公式如下所示: LOC(i) = LOC(0) + i * M
线性数据结构及其处理技术 • 线性表的存储结构及基本运算 • 顺序表上的插入运算 插入运算实现在顺序表的某个指定位置上增加一个新的数据元素。实现顺序表上插入运算的基本过程是: • 在顺序表上确定插入新数据元素的插入点; • 将插入点之后的所有数据元素从后向前依次向后移动一个元素位置,以空出插入位置。在移动的元素中包括插入点原来数据元素时为前插运算,不包括插入点原来数据元素时为后插运算; • 在移动后空出的插入位置上插入新数据元素; • 将顺序表的长度增加一; int insertlist(int i,STU x) { int j; if(n>=N) { printf("overflow!"); getch(); return -1; } if(i<0||i>n) { printf("out of range!"); getch(); return -1; } for(j=n;j>i;j--) stu[j]=stu[j-1]; stu[j]=x; n++; return i; } 设有数据类型和变量定义如下 #define N 100 typedef struct stu { char name[20]; float score; }STU; STU stu[N]; int n=0;
线性数据结构及其处理技术 • 线性表的存储结构及基本运算 • 顺序表上的删除运算 删除运算实现将顺序表中指定位置的数据元素删除。实现顺序表上删除运算的基本过程是: • 在顺序表上确定欲删除数据元素的位置; • 自被删除数据元素之后的所有数据元素依次向前移动一个位置,被删除数据元素被删除(覆盖); • 将顺序表长度减一; 设有数据类型和变量定义如下 #define N 100 typedef struct stu { char name[20]; float score; }STU; STU stu[N]; int n=0; int deletelist(int i) { int j; if(i<0||i>n-1) { printf("no this element!"); getch(); return -1; } for(j=i+1;j<n;j++) stu[j-1]=stu[j]; n--; return i; }
线性数据结构及其处理技术 While(1) 清屏幕 显示菜单 选择菜单项 switch(菜单选项) Case 插入操作 创建一个数据元素 调用插入函数insertlist将数据元素插入顺序表中 Case 删除操作 指定删除数据元素的位置 调用删除函数deletelist删除指定数据元素 Case 打印顺序表 调用printlist函数输出顺序表 Case 退出 退出程序执行 Case 其它 退出Case结构 例9-4 • 线性表的存储结构及基本运算 例9-4顺序表基本操作示例。要求设计一个简单的菜单,根据对菜单项的选择分别实现顺序表的插入操作、顺序表的删除操作和顺序表的输出操作。 解题思想:分别设计出在顺序表中插入一个数据元素的算法: insertlist,在顺序表中删除一个数据元素的算法:deletelist和输出顺序表元素的算法:printlist。在上述算法的基础之上构成顺序表基本操作示例算法 :
∧ Head 数据域 指针域 线性数据结构及其处理技术 • 线性表的存储结构及基本运算 • 线性表的链式存储结构及其运算 线性链表使用一组任意的、可以不连续的存储单元存储线性表的数据元素,此时称线性表中的数据元素为结点。在线性链表的构造中,除第一个结点之外,其余每一个结点的存放位置由该结点的前趋在其指针域中指出。为了能够确定线性链表中的第一个结点的存放位置,使用一个指针指向链表的表头,这个指针称作“头指针”。线性链表的最后一个结点没有后继,为了表示这个概念,该结点的指针域赋值为空(NULL或∧)。链表与顺序表相比有许多优点:①链表结构可以根据处理数据的增减动态增长;②链表结构在进行数据元素的插入和删除操作的时候不需要移动数据元素。但与顺序表比较而言也有许多短处:①链表是一种顺序访问结构;②链表需要指针域用于结点之间的连接,因而链表的存储密度没有顺序表高。 线性表的链式存储结构 结点结构的C语言描述方式如下: typedef struct node { elementtype data; struct node *next; }NODE; 其中,elementtype表示某种已经定义好的表示结点数据域的数据类型;NODE为结点类型struct node的别名。 结点结构
线性数据结构及其处理技术 反向生成法构造单链表算法: NODE *create(int n) /* 构造具有n个结点的单链表 */ { NODE *p,*h; int i; char inbuf[10]; h=(NODE *)malloc(sizeof(NODE)); /* 创建单链表的头结点 */ h->next=NULL; for(i=n;i>0;i--) { p=(NODE *)malloc(sizeof(NODE)); /* 为每一个新结点分配存储 */ gets(p->name); gets(inbuf); p->score=atof(inbuf); p->next=h->next; /* 将新建结点插入到单链表的头结点之后 */ h->next=p; } return h; } • 线性表的存储结构及基本运算 • 带头结点单链表的构造 单链表的构造方法有两种: • 正向生成构造法 单链表的正向生成的步骤主要分为两步:首先创建单链表的头指针,然后将新结点依次链接到单链表的尾部。 • 反向生成构造法 单链表的反向生成的步骤主要分为两步:首先创建单链表的头指针,然后将新结点依次插入到单链表的头部。
线性数据结构及其处理技术 带头结点单链表输出算法: void printlist(NODE *h) { NODE *current=h; while(current->next!=NULL) { current=current->next; printf("%s\t%f\n",current->name,current->score); } } • 线性表的存储结构及基本运算 • 单链表的输出 所谓单链表的输出实质上就是对某一头指针指向的单链表进行遍历,也就是将单链表中的每一个数据元素结点从表头开始依次处理一遍。
p->next=q->next • q->next=p q ① ② p 线性数据结构及其处理技术 带头结点的单链表中实现的插入结点算法: void insertlist(NODE *h,char *s) { NODE *p,*old,*last; char inbuf[20]; /* 创建新结点 */ p=(NODE *)malloc(sizeof(NODE)); printf("\tInput the data of the new node:\n"); gets(p->name); gets(inbuf); p->score=atof(inbuf); • 线性表的存储结构及基本运算 • 单链表的插入运算 实现在单链表上插入一个结点的基本过程如下: • 创建一个新结点; • 按要求寻找插入点; • 被插入结点的指针域指向插入点结点的后继结点; • 插入点结点的指针域指向被插入的结点;
线性数据结构及其处理技术 last=h->next; /* 按某种方法寻找新结点的插入位置 */ while(strcmp(last->name,s)!=0&&last->next!=NULL) { old=last; last=last->next; } if(last->next!=NULL) /* 找到插入位置,插入新结点 */ { old->next=p; p->next=last; } else /* 未找到插入位置,新结点添加到链表末尾 */ { last->next=p; p->next=NULL; } }
① q p ② • q->next=p->next; • free(p); 线性数据结构及其处理技术 带头结点的单链表中实现的删除结点算法: void deletelist(NODE *h,char *s) { NODE *q=h,*p=h->next; /* 定位被删除结点及其前趋*/ while(strcmp(p->name,s)!=0&&p->next!=NULL) { q=p; p=p->next; } if(p->next!=NULL) /*找到被删除结点则将其从链表中删去*/ { q->next=p->next; free(p); } else /*找不到被删除结点则给出提示信息*/ { printf("no this element!\n"); getch(); } } • 线性表的存储结构及基本运算 • 单链表的删除运算 实现在单链表中删除一个数据元素结点的基本过程如下: • 查找被删除结点以及其前趋结点; • 被删除结点的前趋结点指针域指向被删除结点的直接后继结点; • 释放被删除结点;
线性数据结构及其处理技术 • 线性表的存储结构及基本运算 例9-5 带头结点单链表基本操作示例。要求设计一个简单的菜单,根据对菜单项的选择分别实现带头结点单链表的构造操作、插入操作、删除操作和输出操作。 解题思想:分别设计出对带头结点单链表操作算法如下: • 构造具有n个结点的单链表 NODE *create(int n); • 在单链表中插入一个新的结点 void insertlist(NODE *h, char *s); • 在单链表中删除一个指定结点 void deletelist(NODE *h, char *s); • 输出单链表 void printlist(NODE *h);
线性数据结构及其处理技术 • 栈的基本概念和应用 • 栈的基本概念 栈是限定只能在一端进行插入操作和删除操作的线性表,允许进行插入和删除操作的一端称为栈的栈顶,不允许进行插入和删除操作的一端称为栈底。一般使用一个栈顶指针p来指示栈顶,当栈顶指针p=0时,栈中无数据元素,称为“空栈”。对栈而言,常用的基本运算有: • 设置一个空栈; • 进栈(压栈、入栈),即在栈顶加入一个新的数据元素; • 出栈(退栈、弹出),即删除栈顶数据元素; • 访问栈顶数据元素,与出栈不同的是此时数据元素不出栈; • 测试栈是否为空;
线性数据结构及其处理技术 • 栈的基本概念和应用 • 栈的顺序存储结构及其操作 在顺序存储结构上实现的栈称为顺序栈。C语言中使用一维数组来表示栈的顺序存储结构,数组的长度即为栈的最大容量。 • 设置一个栈顶指针top用来表示栈顶位置,约定top指向下一个应该存放数据元素的存储单元。 • 若栈的容量为M,则当top=M时称为“栈满”,此时若还向栈内进栈数据元素就会出现栈的“上溢出”。 • 当top=0时表示“空栈”,此时若还要进行出栈操作,就会出现栈的“下溢出”。
线性数据结构及其处理技术 • 栈的基本概念和应用 • 栈的顺序存储结构示例:一个字符栈的进栈运算和出栈运算。 • 顺序栈相关定义如下: #define MAX 100 /*栈的最大容量*/ char stack[MAX]; /*定义字符栈*/ int top=0; /*设置栈为空*/ /*字符栈的出栈操作算法*/ char pop() { char ch; if(top==0) { printf("Stack is empty.\n"); exit(1); } else ch=stack[--top]; return ch; } /*字符栈的进栈操作算法*/ void push(char c) { if(top>=MAX-1) { printf("Stack is full.\n"); exit(1); } else stack[top++]=c; }
线性数据结构及其处理技术 • 栈的基本概念和应用 例9-6 编制程序实现通过栈进行颠倒字符串操作。 解题思想: • 定义字符数组表示字符顺序栈; • 设置栈顶指针表示下一存放数据元素位置,初值为0; • 输入被处理的字符串; • 调用进栈算法从字符串首开始依次对所有字符执行进栈操作; • 调用出栈算法从栈中弹出字符依次存放到原字符串中; • 输出处理后的字符串;
Stack ∧ 线性数据结构及其处理技术 • 栈的基本概念和应用 • 栈的链式存储结构及其操作 在链式存储结构上实现的栈称为链栈,这种单端操作受限的链表的第一个结点就是链栈的栈顶结点,指向栈顶结点的指针stack称为栈顶指针。 • 链栈的显著优点是不会出现因栈满而出现“上溢出”现象,只有当存储分配失败是才会出现错误。
线性数据结构及其处理技术 /*字符栈的进栈操作算法*/ void push(char c) { NODE *p; p=(NODE*)malloc(sizeof(NODE)); p->data=c; p->next=stack; stack=p; } /*字符栈的出栈操作算法*/ char pop() { char ch; NODE *p; if(stack==NULL) { printf("Stack is empty.\n"); exit(1); } else { p=stack; stack=stack->next; ch=p->data; free(p); } return ch; } • 栈的基本概念和应用 • 栈的链式存储结构示例:不带头结点的字符链栈的进栈运算和出栈运算。 • 链栈的相关定义 typedef struct node { char data; struct node *next; }NODE; NODE *stack; 例9-7 编制程序实现通过栈进行颠倒字符串操作。
线性数据结构及其处理技术 • 队列的基本概念和应用 • 队列的基本概念 队列是双端受限的线性表。队列的插入操作只能在表的一端进行,允许插入操作的一端称为队尾(rear)。队列的删除操作只能在表的另外一端进行,允许删除操作的一端称为对头(front)。队列具有“先进先出(FIFO)”的特征。队列的基本运算有: • 建立一个空队列; • 入队操作,即在队尾添加一个新的数据元素; • 出队操作,即将对头元素删除; • 测试队列是否空对列; • 测试队列是否满队列;
E1 E2 E3 E4 rear front 线性数据结构及其处理技术 • 队列的基本概念和应用 • 队列的顺序存储结构 队列的顺序存储结构使用一维数组实现,一维数组的大小就是队列的最大长度。设置两个用于表示“队头”和“队尾”的变量front和rear,通常约定队头指针front指示队列中队头数据元素所处位置的前一个位置;队尾指针rear指示队列中队尾数据元素所处的当前位置。
E3 E4 E5 rear front 线性数据结构及其处理技术 • 队列的基本概念和应用 • 队列的“假溢出”现象 在队列的操作过程中,队头指针front和队尾指针rear都会逐渐向数组尾部移动。经过若干次入队和出队操作后,有可能会出现如图所示现象,队尾指针rear已经移动到了数组的上界,再进行入队操作就会“上溢出”;但此时队头指针之前还有空单元,这种现象称为“假溢出”现象。 • 队列的“假溢出”解决方法 解决“假溢出”问题的一个有效方法是使用“循环队列”,所谓循环队列就是将数组中的0号元素位置作为其最后一个数据元素位置的直接后继。
线性数据结构及其处理技术 • 队列的基本概念和应用 • 循环队列中位置指针的移动 • 两个指针在长度为M的循环队列中移动的方法为: • front = (fornt+1)%M • rear = (rear+1)%M • 循环队列中 “空队列”和“满队列”的判断 • 空队列 rear = front • 满队列 (rear+1)%M = front
线性数据结构及其处理技术 例9-8循环队列基本操作示例。要求设计一个简单的菜单,根据对菜单项的选择分别实现循环队列的入队操作、循环队列的出队操作和和循环队列中所有元素的输出操作。 • 队列的基本概念和应用 • 循环队列的入队算法和出队算法 相应定义如下: #define N 20 typedef struct stu { char name[20]; float score; }STU; STU Que[N]; int front=0,rear=0; /*循环队列的出队算法*/ STU deleteQueue() { STU x; if(front==rear) { printf("Empty Queuer.\n"); exit(1); } else { front=(front+1)%N; x=Que[front]; } return x; } /*循环队列的入队算法*/ int addQueue(STU x) { if((rear+1)%N==front) { printf("Full Queuer.\n"); return -1; } else { rear=(rear+1)%N; Que[rear]=x; } return 0; }
∧ front rear ∧ front rear 线性数据结构及其处理技术 • 队列的基本概念和应用 • 队列的链式存储结构及其操作 队列的链式存储结构称为链队。链队实质上就是一个同时带有头指针和尾指针的单链表,头指针指向队头,尾指针指向队尾。一般使用带头结点的单链表来表示链队,如图所示,当队列为空时队头指针和队尾指针都指向头结点。 非空链队 空链队
线性数据结构及其处理技术 例9-9链队基本操作示例。要求设计一个简单的菜单,根据对菜单项的选择分别实现在带头结点链队上的入队操作、出队操作和,并能够输出(显示)链队中的所有数据元素。 • 队列的基本概念和应用 • 带头结点的链队上实现的入队算法和出队算法 NODE类型定义如下: typedef struct stu { char name[20]; float score; struct stu *next; }NODE; • 为了创建一个带头结点的空队列,需要在程序适当的地方使用下面两条语句: front=rear=(NODE *)malloc(sizeof(NODE));rear->next=NULL; /*在链队上的入队算法,将x入队*/ void addQueue(NODE x) { NODE *p; p=( NODE *)malloc(sizeof(NODE)); strcpy(p->name,x.name); p->score=x.score; p->next=NULL; rear->next=p; rear=p; } /*在链队上的出队算法*/ void deleteQueue() { NODE *p; if(front==rear) { printf("Empty Queue!\n"); exit(1); } p=front->next; front->next=p->next; if(p->next==NULL) rear=front; free(p); }
C语言应用 • 线性数据结构及其处理技术 • 常用的排序技术 • 常用查找技术 • 图形程序设计基础
常用的排序技术 • 排序的概念 • 排序就是将数据集中的一组记录型数据(C语言中的结构体数据)按记录数据中的某一个关键字的递增或递减的次序排列成为一个满足要求的有序序列。排序分为内部排序和外部排序: • 内部排序 被处理的数据全部进入计算机系统的内(主)存储器,整个排序过程都在计算机系统的内存储器中完成。 • 外部排序 当被处理的数据量非常大,不能或者不适宜全部放到计算机系统的内存储器时采用外部排序方式。外部排序时,一部分待排数据放到计算机系统的外(辅助)存储器中,在排序的过程中有需要时才将这些数据调入内存储器。
常用的排序技术 • 直接选择排序 • 选择排序基本思想 对于待排的n个记录,在其中寻找关键字值最大(或最小)的记录,并将其移动到表的最前面作为其第一个记录;在剩下的N-1个记录中用相同的方法寻找关键字值最大(或最小)的记录,并将其作为其第二个记录;以此类推,直到将整个待排数据集合处理完为止(只剩下一个待处理记录)。
常用的排序技术 • 直接选择排序 • 直接选择排序的基本方法 • ①在所有的记录中选取关键字值最大(或最小)的记录,并将其与第一个记录交换位置; • ②将上次操作完成后剩下的记录中构成一个新处理数据集; • ③新处理数据集的所有记录中选取关键字值最大(或最小)的记录,并将其与新处理数据集中第一个记录交换位置; • ④如果还有待处理记录,转到② 。 typedef struct word { char w[20]; int count; }WORD; void selectsort(WORD v[],int n) { int i,j,k; WORD t; for(i=0;i<n-1;i++) { k=i; for(j=i+1;j<n;j++) if(v[j].count>v[k].count) k=j; if(k!=i) t=v[i],v[i]=v[k],v[k]=t; } } 对一个具有n个记录的数据集合进行排序时,共要进行n-1趟,比较次数构成了一个等差数列,其比较总次数为: 记直接选择排序的平均时间复杂度为O(n2)。 例9-10录入部分单词和其使用频率,将它们按其词频降序排序后输出。
常用的排序技术 • 插入排序 • 插入排序基本思想 插入排序的基本概念是将待排记录按其关键字值的大小插入到一个记录的有序序列中适当位置上使其仍然保持有序,直到待排记录全部插入完成为止。 • 常用插入排序方法 • 直接插入排序 • 二分插入排序
常用的排序技术 #define N 10 typedef struct word { char w[20]; int count; }WORD; void insertsort(WORD v[],int n) { int i,j; for(i=2;i<=n;i++) { v[0]=v[i]; for(j=i-1;v[0].count>v[j].count;j--) v[j+1]=v[j]; v[j+1]=v[0]; } } • 直接插入排序方法 直接插入排序指的是在有序序列中寻找插入位置时使用线性搜索的方法,整个排序过程可以描述如下: • ①将第一个记录作为有序序列,其余的待排记录作为待排序列; • ②取出待排序列中第一个,在有序序列中寻找其应该插入的位置; • ③将有序序列中自插入位置开始的所有记录从后到前依次移动一个位置,空出插入位置; • ④将记录放置到插入位置上; • ⑤如果带排序列中还有待排记录,返回②,直到处理完为止。 n个记录的直接插入排序在最坏情况下需要进行n-1趟,最多总比较次数为: 记直接选择排序的平均时间复杂度为O(n2)。 例9-11录入部分单词和其使用频率,将它们按其词频降序排序后输出。
常用的排序技术 void binsertsort(WORD v[],int n) { int i,j,low,high,mid; for(i=2;i<=n;i++) { v[0]=v[i]; low=1,high=i-1; while(low<=high) { mid=(low+high)/2; if(v[0].count>v[mid].count) high=mid-1; else low=mid+1; } for(j=i-1;j>=low;j--) v[j+1]=v[j]; v[low]=v[0]; } } • 二分插入排序方法 二分插入排序指的是在有序序列中寻找插入位置时使用二分搜索的方法,当在有序序列中找到插入位置后,将从该位置及其以后的所有记录从后到前依次向后移动一个位置,空出插入位置来插入记录。用于插入排序的二分搜索算法如下: While(有序序列中还有记录没有搜索) 计算有序序列的中点; If(中点位置记录关键字值大于用于查找的关键字值) 将有序序列的高端指针移动到中点之前,进行下一轮查找; Else 将有序序列的低端指针移动到中点之后,进行下一轮查找; 二分插入排序的效率比直接插入排序高,在比较上的时间复杂度为O(nlog2n),但由于其在移动次数上与直接插入排序持平,仍为O(n2),所以其平均时间复杂度仍然也为O(n2)。 例9-12录入部分单词和其使用频率,将它们按其词频降序排序后输出。
常用的排序技术 #define N 10 typedef struct word { char w[20];int count; }WORD; void bubblesort(WORD v[],int n) { int i,j,flag=1; /*标志flag初值为1*/ i=n-1; /*最多进行n-1趟排序扫描过程*/ while(i>0&&flag) /*趟数未完并且还需要排序*/ { flag=0; /*每一趟开始时设置标志flag=0*/ for(j=1;j<=i;j++) if(v[j+1].count>v[j].count) { flag=1; /*若有交换,标志flag=1*/ v[0]=v[j],v[j]=v[j+1],v[j+1]=v[0]; } i--; } } 最坏的情况下,对n个记录的冒泡排序算法需要进行n-1趟,其总的比较次数为: 排序时最多的移动记录次数为: 冒泡排序算法的平均时间复杂度为O(n2)。 例9-13录入部分单词和其使用频率,将它们按其词频降序排序后输出。 • 冒泡排序 冒泡排序算法是交换排序中比较简单的一种排序方法,交换排序的基本思想是两两比较带排序记录的关键字值,根据比较结果来对换这两个记录在序列中的位置。就冒泡排序法而言,其算法基本概念可描述如下: • ①从较待排序列中第一个位置开始,依次比较相邻两个位置上的记录,若是逆序则交换,一趟扫描后,关键字值最大(或最小)的记录别交换到了最右边; • ②不考虑已排好序的记录,将剩下的记录作为待排序列; • ③重复①、②两步直到排序完成,n个记录的排序最多进行n-1趟;
常用的排序技术 • 希尔排序 希尔排序的基本思想是先对相隔较远的记录进行比较,而不是像简单交换排序算法那样比较相邻的两个记录,这样可以以较快的速度减少大量的无序情况。每进行一趟比较和交换后,被比较的记录之间的相隔距离逐步减小,一直减少到间隔距离为1,此时为最后一趟排序,最后一趟排序时进行的是相邻位置记录的比较和交换。
常用的排序技术 • 希尔排序的具体方法 ①将整个待排序列按给定的增量(跨距)划分为若干个子待排序列; ②对每一个待排子序列分别进行插入排序,使得该子序列为有序序列; ③减小增量(跨距); ④以修改后的增量反复进行①~③,直到增量为1时进行最后一趟排序 后结束; #define N 10 typedef struct word { char w[20]; int count; }WORD; void shellsort(WORD v[],int n) { int i,j,gap; WORD temp; for(gap=n/2;gap>0;gap/=2) for(i=gap;i<n;i++) for(j=i-gap;j>=0&&v[j].count<v[j+gap].count;j-=gap) temp=v[j],v[j]=v[j+gap],v[j+gap]=temp; } 当参与排序的记录个数非常大时,希尔排序的时间复杂度趋近于: 记录的关键字序列(38,26,54,87,65,16,3,26) 增量分别取4、2、1 例9-14录入部分单词和其使用频率,将它们按其词频降序排序后输出。
void quicksort(WORD v[],int left,int right) { void swap(WORD v[],int i,int j); int i,last; WORD t; if(left>=right) return; swap(v,left,(left+right)/2); last=left; for(i=left+1;i<=right;i++) if(v[i].count>v[left].count) swap(v,++last,i); swap(v,left,last); quicksort(v,left,last-1); quicksort(v,last+1,right); } void swap(WORD v[],int i,int j) { WORD t; t=v[i]; v[i]=v[j]; v[j]=t; } 常用的排序技术 • 快速排序 快速排序也是交换排序中的一种方法,其基本思想是: ①在待排序列中任意取一个记录,以它为基准用交换的方法将所有的待 排记录分为两个部分,此时整个待排记录序列以该记录为准分为了两 个子待排序列,一个子序列中记录的关键字值小于基准记录的关键字 值,另外一个子序列中记录的关键字值大于基准记录的关键字值。 ②对每一个待排的子序列重复①的操作,以划分为更小的待排子序列; ③重复进行②,直到每一个待排子序列都只有一个记录时排序完成; 例9-15录入部分单词和其使用频率,将它们按其词频降序排序后输出。
C语言应用 • 线性数据结构及其处理技术 • 常用的排序技术 • 常用查找技术 • 图形程序设计基础
常用查找技术 • 查找的概念 查找也称为检索,其基本概念就是在一个记录的集合中找出符合某种条件的记录。查找的结果有两种:在表中如果找到了与给定的关键字值相符合的记录,称为成功的查找,根据需要可以获取所找记录的数据信息或给出记录的位置。若在表中找不到与给定关键字值相符合的记录,则称为不成功的查找,给出提示信息或空位置指针。 • 评价查找方法优劣的主要因素 • 查找的速度; • 实现查找所需要占用的存储空间大小; • 查找算法本身的复杂程度;
常用查找技术 • 平均查找长度 • 查找长度定义为为了查找某个记录所需要进行和给定关键字进行比较的次数。对于长度为n的表而言,其平均查找长度(ASL:Average Search Length)为: • Pi为查找表中第i个记录的概率 • Ci为查找表中第i个记录所需要的比较次数 常常用平均查找长度来分析查找算法的效率, 当用ASL来分析查找算法的好坏时,ASL 值越小,表示查找的次数越少,则相应的查找算法的效率越高。
常用查找技术 • 线性查找 线性查找又称为顺序查找。其基本过程是:从表中的第一个记录开始,将给定的关键字值与表中每一个记录的关键字值逐个进行比较。如果找到相符合的记录时,查找成功,如果查找到标得末端都未找到相符合的记录时,查找失败。
#define N 10 typedef struct word { char w[20]; int count; }WORD; int listsearch(WORD v[],int key,int n) { int i; v[0].count=key; for(i=n;v[i].count!=key;i--) ; return i; } 假定在具有n个记录的查找序列上的每一个记录被查找的概率p=1/n,即查找任何一个记录的概率相同,为查找第i个记录所需的比较次数为Ci,可以得到平均查找长度为: 例9-16录入部分单词和其使用频率,输出第一个满足指定使用频率的单词信息(包括在表中的位置、使用次数和单词内容)。 查找算法当查找成功是返回记录的位置,查找失败是返回0。 常用查找技术
常用查找技术 • 二分查找 二分查找法又称为折半查找法,该算法要求在一个对查找关键字而言有序的序列上进行,其基本思想是:逐步缩小查找目标可能存在的范围,具体描述如下: ①选取表中中间位置的记录作为基准,将表分为两个子表; ②当基准记录的关键字值与查找的关键字值相符合时,返回基准记录位置,算法结束; ③当基准记录的关键字值与查找的关键字值不符合时,在处理的两个子表中选取一个子表,重复执行①、 ②,直到被处理的子表中没有记录为止。
常用查找技术 typedef struct word { char w[20]; int count; }WORD; int binarysearch(WORD v[],int key,int n) { int low=0,high=n-1,mid; while(low<=high) { mid=(low+high)/2; if(key>v[mid].count) low=mid+1; else if(key<v[mid].count) high=mid-1; else return mid; } return -1; } 二分查找法只能对顺序存储的有序表进行操作。 二分查找法的平均查找长度为: 例9-17录入部分单词和其使用频率,输出第一个满足指定使用频率的单词信息(包括在表中的位置、使用次数和单词内容)