D1 数据结构引入
多敲多练与智商无关
初识数据结构
数据结构解决什么问题
数据结构举例
计算机处理的对象(数据)已不再单纯是数值
大学系级行政机构
设田径比赛项目有:A(跳高)、 B(跳远)、C(标枪)、D(铅球)、E(100m跑)、F(200m跑)。参赛选手的项目表,如下表所列:
问如何安排比赛时间,才能使得 :
1)每个比赛项目都能顺利进行(无冲突)
2) 尽可能缩短比赛时间
上题转化为图的“染色“问题:设项目A∽F各表示一数据元素,以○表示。若两个项目不能同时举行,则将其连线。若用四种颜色表示四个时间段,一种着色方案如图所示。
即:红色时间段(如8~10点)—A、C项目;浅蓝— B、D项目;深蓝— E项目; 紫色—F项目。
数据的逻辑结构
数据元素之间存在某种关系
什么是数据结构
什么是数据结构
1968年美国克努特教授开创了数据结构的最初体系;
计算机的圣经-《计算机程序设计的艺术》
荣获1974年度的图灵奖
第一卷《基本算法》
第二卷《半数字化算法》
第三卷《排序与搜索》
第四卷《组合算法》
- 内涵
- 研究计算机数据间的关系
- 包括数据的逻辑结构和存储结构及其操作
基本概念
1.数据(Data)
数据即信息的载体,是能够输入到计算机中并且能被计算机识别、存储和处理的符号总称。
2.数据元素(Data Element)
数据元素是数据的基本单位,又称之为记录(Record)。一般,数据元素由若干基本项(或称字段、域、属性)组成。
数据的逻辑结构
表示数据运算之间的抽象关系
按每个元素可能具有的直接前趋数和直接后继数将逻辑结构分为“线性结构”和“非线性结构”两大类。
数据的存储结构
存储结构:逻辑结构在计算机中的具体实现方法。
存储结构是通过计算机语言所编制的程序来实现的,因而是依赖于具体的计算机语言的。
顺序存储(Sequential Storage):
将数据结构中各元素按照其逻辑顺序存放于存储器一片连续的存储空间中
如c语言的一维数组,如表 L=(a1,a2,……,an)的顺序结构
链式存储(重点)
将数据结构中各元素分布到存储器的不同点,用地址(或链指针)方式建立它们之间的联系
数据结构中元素之间的关系在计算机内部很大程度上是通过地址或指针来建立的。
索引存储
在存储数据的同时,建立一个附加的索引表,即索引存储结构=数据文件+索引表。
散列存储
根据数据元素的特殊字段(称为关键字key),计算数据元素的存放地址,然后数据元素按地址存放
数据结构的三个方面
- 存储结构
- 顺序存储,空间上连续
- 链式存储,将数据结构中各元素分布到存储器的不同点
- 索引存储,索引存储结构=数据文件+索引表
- 散列存储,根据数据元素中的特殊字段计算数据元素的存放地址
- 数据的运算
- 检索
- 排序
- 插入
- 删除
- 修改
D2 线性表-顺序表
线性表
什么是线性表?
数据结构举例
目录结构
线性表
线性表是包含若干数据元素的一个线性序列
记为: L=(a0, … ai-1, ai, ai+1 … an-1)
L为表名,ai (0≤i≤n-1)为数据元素;
n为表长,n>0 时,线性表L为非空表,否则为空表。
线性表L可用二元组形式描述:
L= (D,R)
即线性表L包含数据元素集合D和关系集合R
D={ai | ai∈datatype ,i=0,1,2, ∙∙∙∙∙∙∙∙∙n-1 ,n≥0}
R={<ai , ai+1> | ai , ai+1∈D, 0≤i≤n-2}
关系符<ai, ai+1>在这里称为有序对
表示任意相邻的两个元素之间的一种先后次序关系
ai是ai+1的直接前驱, ai+1是ai的直接后继
线性表的特征:
- 对非空表,a0是表头,无前驱;
- an-1是表尾,无后继;
- 其它的每个元素ai有且仅有一个直接前驱ai-1和一个直接后继ai+1。
线性表顺序存储
顺序存储结构的表示
若将线性表L=(a0,a1, ……,an-1)中的各元素依次存储于计算机一片连续的存储空间。
设Loc(ai)为ai的地址,Loc(a0)=b,每个元素占d个单元 则:Loc(ai)=b+i*d
顺序存储结构的特点
逻辑上相邻的元素 ai, ai+1,其存储位置也是相邻的
对数据元素ai的存取为随机存取或按地址存取
存储密度高
存储密度D=(数据结构中元素所占存储空间)/(整个数据结构所占空间)
顺序存储结构的不足:
对表的插入和删除等运算的时间复杂度较差。
在C语言中,可借助于一维数组类型来描述线性表的顺序存储结构
#define N 100
typedef int data_t;
typedef struct
{ data_t data[N]; //表的存储空间
int last;
} sqlist, *sqlink;
顺序表的实现
线性表的基本运算
设线性表 L=(a0,a1, ……,an-1),对 L的基本运算有:
1)建立一个空表:list_create(L)
2)置空表:list_clear(L)
3)判断表是否为空:list_empty (L)。若表为空,返回值为1 , 否则返回 0
4)求表长:length (L)
5)取表中某个元素:GetList(L , i ), 即ai。要求0≤i≤length(L)-1
6)定位运算:Locate(L,x)。确定元素x在表L中的位置(或序号)
基本运算的相关算法
7)定位
确定给定元素x在表L中第一次出现的位置(或序号)。即实现Locate(L,x)。算法对应的存储结构如图所示。
8)插入
Insert(L,x,i)。将元素x插入到表L中第i个元素ai之前,且表长+1。
插入前: (a0,a1,—,ai-1,ai,ai+1-------,an-1) 0≤i≤n,i=n时,x插入表尾
插入后: (a0,a1,—,ai-1, x, ai,ai+1-------,an-1)
算法思路:若表存在空闲空间,且参数i满足:0≤i≤L->last+1,则可进行正常插入。插入前,将表中(L->data[L->last]~L->data[i])部分顺序下移一个位置,然后将x插入L->data[i]处即可。算法对应的表结构。
9)删除
Delete(L,i)。删除表L中第i个元素ai,且表长减1, 要求0≤i≤n-1。
删除前: (a0,a1,—,ai-1,ai,ai+1-------,an-1)
删除后: (a0,a1,—,ai-1,ai+1-------,an)
删除:将表中第i个元素ai从表中删除,即实现DeleteSqlist(L, i)。
算法思路: 若参数i满足:0≤i≤L->last, 将表中L->data[i+1]∽L->data[L->last] 部分顺序向上移动一个位置,覆盖L->data[i]。
设线性表La=(a0a1, ……,am-1), Lb= (b0b1, ……,bn-1),求La∪Lb =>La,
算法思路:依次取表Lb中的bi(i=0,1,……,n-1),若bi不属于La,则将其插入表La中。
设计清除线性表L=(a0,a1,—,ai,-------,an-1)中重复元素的算法。
算法思路:对当前表L中的每个ai(0≤i≤n-2),依次与aj(i+1≤j≤n-1) 比较,若与ai相等,则删除之。
线性表的顺序存储缺点
线性表的顺序存储结构有存储密度高及能够随机存取等优点,但存在以下不足:
(1)要求系统提供一片较大的连续存储空间。
(2)插入、删除等运算耗时,且存在元素在存储器中成片移动的现象;
D3 顺序表的实现
顺序表的实现2
- 利用结构体分配堆内存,分配内存后要强制转换,要判断分配成功没有,要引入<stdlib .h>
- melloc()成功返回一个指针,否则返回NULL
- memset(void *s,int c,size_t n );从s开始的n个字节全部用c去填充。置空函数,引入<string.h>
顺序表的实现3
- 插入时条件
- 检查线性表是否未满
- 位置是[0,last+1]
- 移动是从后往前
- 存新值后last++
顺序表的实现4
- 注意C语言中没有重载,函数不能重名。
- 避免函数重名的方法:
- 在源文件中,不对外提供的函数,定义时都用static修饰,将该函数的链接属性限定在本文件;
- 函数命名时,在每个函数的名字前都加上库的名称,一般库的名字是唯一且不重合的;
- 全局变量如果只在本文件中使用,最好也使用static修饰;
- 避免函数重名的方法:
- 合并线性表
- 步骤
- 先查在不在表中,不在则插入
- 步骤
顺序表的实现5
- 外层循环从1开始,内层循环小于外层循环,逐一和外层循环比较,如果相同就删除外层循环当前位置的元素,否则外层循环加一;
顺序表的结尾
- 优点:存储连续,查找快
- 缺点:需要初始化,需要大块连续空间,插入删除耗时,存在元素在存储器需要成片移动的现象。
代码
sqlist.h
typedef int data_t;
#define N 128
typedef struct {
data_t data[N];
int last;
}sqlist, *sqlink;
sqlink list_create();
int list_clear(sqlink L);
int list_free(sqlink L);
int list_empty(sqlink L);
int list_length(sqlink L);
int list_locate(sqlink L, data_t value);
int list_insert(sqlink L, data_t value, int pos);
int list_delete(sqlink L, int pos);
int list_merge(sqlink L1, sqlink L2);
int list_purge(sqlink L);
int list_show(sqlink L);
sqlist.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sqlist.h"
sqlink list_create() {
//malloc
sqlink L;
L =(sqlink)malloc(sizeof(sqlist));
if (L == NULL) {
printf("list malloc failed\n");
return L;
}
//initialize
memset(L, 0, sizeof(sqlist));
L->last = -1;
//return
return L;
}
/*
* @ret 0-success -1-failed
* */
int list_clear(sqlink L) {
if (L == NULL)
return -1;
memset(L, 0, sizeof(sqlist));
L->last = -1;
return 0;
}
int list_free(sqlink L){
if (L == NULL)
return -1;
free(L);
L = NULL;
return 0;
}
/*
* list_empty: Is list empty?
* para L: list
* @ret 1--empty 0--not empty
* */
int list_empty(sqlink L) {
if (L->last == -1)
return 1;
else
return 0;
}
int list_length(sqlink L) {
if (L == NULL)
return -1;
return (L->last+1);
}
/*
* @ret -1--not exist pos
* */
int list_locate(sqlink L, data_t value) {
int i ;
for (i = 0; i <= L->last; i++) {
if (L->data[i] == value)
return i;
}
return -1;
}
int list_insert(sqlink L, data_t value, int pos) {
int i;
//full
if (L->last == N-1) {
printf("list is full\n");
return -1;
}
//check para 0<=pos<=Last+1 [0, last+1]
if (pos < 0 || pos > L->last+1) {
printf("Pos is invalid\n");
return -1;
}
//move
for (i = L->last; i >= pos; i--) {
L->data[i+1] = L->data[i];
}
//update value last
L->data[pos] = value;
L->last++;
return 0;
}
int list_show(sqlink L) {
int i;
if (L == NULL)
return -1;
if (L->last == -1)
printf("list is empty\n");
for (i = 0; i <= L->last; i++) {
printf("%d ", L->data[i]);
}
puts("");
return 0;
}
int list_delete(sqlink L, int pos) {
int i;
if (L->last == -1) {
printf("list is empty\n");
return -1;
}
//pos [0, last]
if (pos < 0 || pos > L->last) {
printf("delete pos is invalid\n");
return -1;
}
//move [pos+1, last]
for (i = pos+1; i <= L->last; i++) {
L->data[i-1] = L->data[i];
}
//update
L->last--;
return 0;
}
int list_merge(sqlink L1, sqlink L2) {
int i = 0;
int ret;
while (i <= L2->last){
ret = list_locate(L1, L2->data[i]);
if (ret == -1) {
if (list_insert(L1, L2->data[i], L1->last+1) == -1)
return -1;
}
i++;
}
return 0;
}
int list_purge(sqlink L) {
int i;
int j;
if (L->last == 0)
return 0;
i = 1;
while (i <= L->last) {
j = i-1;
while (j >= 0) {
if (L->data[i] == L->data[j]) {
list_delete(L, i);
break;
} else {
j--;
}
}
if ( j < 0) {
i++;
}
}
return 0;
}
test.c
#include <stdio.h>
#include "sqlist.h"
void test_insert();
void test_delete();
void test_merge();
void test_purge();
int main(int argc, const char *argv[])
{
//test_insert();
//test_delete();
//test_merge();
test_purge();
return 0;
}
void test_insert() {
sqlink L;
L = list_create();
if (L == NULL)
return;
list_insert(L, 10, 0);
list_insert(L, 20, 0);
list_insert(L, 30, 0);
list_insert(L, 40, 0);
list_insert(L, 50, 0);
list_insert(L, 60, 0);
list_show(L);
//list_insert(L, 100, list_length(L));
list_insert(L, 100, -1000);
list_show(L);
list_free(L);
}
void test_delete() {
sqlink L;
L = list_create();
if (L == NULL)
return;
list_insert(L, 10, 0);
list_insert(L, 20, 0);
list_insert(L, 30, 0);
list_insert(L, 40, 0);
list_insert(L, 50, 0);
list_insert(L, 60, 0);
list_show(L);
list_delete(L, 9);
list_show(L);
list_free(L);
}
void test_merge() {
sqlink L1, L2;
L1 = list_create();
if (L1 == NULL)
return;
L2 = list_create();
if (L2 == NULL)
return;
list_insert(L1, 10, 0);
list_insert(L1, 20, 0);
list_insert(L1, 30, 0);
list_insert(L1, 40, 0);
list_insert(L2, 50, 0);
list_insert(L2, 20, 0);
list_insert(L2, 90, 0);
list_insert(L2, 40, 0);
list_show(L1);
list_show(L2);
printf("********************\n");
list_merge(L1, L2);
list_show(L1);
list_show(L2);
}
void test_purge() {
sqlink L;
L = list_create();
if (L == NULL)
return;
list_insert(L, 10, 0);
list_insert(L, 10, 0);
list_insert(L, 10, 0);
list_insert(L, 10, 0);
list_insert(L, 10, 0);
list_insert(L, 10, 0);
list_show(L);
list_purge(L);
list_show(L);
list_free(L);
}
D4 线性表之链表
单链表
线性表的链式存储结构
将线性表L=(a0,a1,……,an-1)中各元素分布在存储器的不同存储块,称为结点,通过地址或指针建立元素之间的联系
结点的data域存放数据元素ai,而next域是一个指针,指向ai的直接后继ai+1所在的结点。
设线性表L=(赵,钱,孙,李,周,吴,郑,王),各元素在存储器中的分布如图
在本书的讨论中,为了便于运算的实现,在单链表的第一个结点之前增设一个类型相同的结点,称之为头结点,其他结点称为表结点。表结点的第一个和最后一个结点分别就是首结点 和尾结点。
头结点不放数据,头结点的下一个,即首结点放数据。
结点类型描述:
typedef struct node
{ data_t data; //结点的数据域//
struct node *next; //结点的后继指针域//
}listnode, *linklist;
设p指向链表中结点ai
获取ai,写作:p->data;
而取ai+1,写作:p->next->data
若指针p的值为NULL,则它不指向任何结点, 此时取p->data或p->next是错误的。
可调用C语言中malloc()函数向系统申请结点的存储空间
linklist p;
p = (linklist)malloc(sizeof(listnode));
则创建一个类型为linklist的结点,且该结点的地址已存入指针变量p中:
链表的实现
基本运算的相关算法
建立单链表
依次读入表L=(a0,…,an-1)中每一元素ai(假设为整型),若ai≠结束符(-1),则为ai创建一结点,然后插入表尾,最后返回链表的头结点指针H。
设L=(2,4,8,-1),则建表过程如下:
链表的结构是动态形成的,即算法运行前,表结构是不存在的
链表查找
1)按序号查找:实现GetLinklist(h, i)运算。
算法思路:从链表的a0起,判断是否为第i结点,若是则返回该结点的指针,否则查找下一结点,依次类推。
2)按值查找(定位) : 即实现Locate(h, x)。
算法思路:从链表结点a0起,依次判断某结点是否等于x,若是,则返回该结点的地址,若不是,则查找下一结点a1,依次类推。若表中不存在x,则返回NULL。
链表的插入:即实现InsertLinklist(h, x, i,)。将x插入表中结点ai之前的情况。
算法思路:调用算法GetLinklist(h, i-1),获取结点ai-1的指针p(ai 之前驱),然后申请一个q结点,存入x,并将其插入p指向的结点之后。
链表的删除:即实现DeleteLinklist(h, i), 算法对应的链表结构如图所示。
算法思路:同插入法,先调用函数GetLinklist(h, i-1),找到结点ai的前驱,然后将结点ai删除之。
D5 线性表之链表应用
单链表实现-链表删除
- 步骤
- 判断空链
- 找位置 pos
- 找前驱
- 判断当前位置即前驱的后继是否为空
- 删除 p->next = p->next->next
- 释放空间,置空指针
- 多少个maloc 多少个free
单链表实现-链表释放
- 步骤,思路同插入法
- 判空
- 遍历释放
- 注意将调用free()方法的连表的只想foujiedijjj
D6 单链表的操作实现
链表的复杂操作的实现
1、设计算法,将单链表H倒置
算法思路:依次取原链表中各结点,将其作为新链表首结点插入H结点之后
2、设结点data域为整型,求链表中相邻两结点data值之和为最大的第一结点的指针。
算法思路:设p,q 分别为链表中相邻两结点指针,求p->data+q->data为最大的那一组值,返回其相应的指针p即可
3、设两单链表A、B按data值(设为整型)递增有序,将表A和B合并成一表A,且表A也按data值递增有序。
算法思路:设指针p、q分别指向表A和B中的结点,若p->data ≤q->data则p结点进入结果表,否则q结点进入结果表。
代码
linklist.h
typedef int data_t;
typedef struct node {
data_t data;
struct node * next;
}listnode, * linklist;
linklist list_create();
int list_tail_insert(linklist H, data_t value);//head
linklist list_get(linklist H, int pos);
int list_insert(linklist H, data_t value, int pos);
int list_delete(linklist H, int pos);
int list_show(linklist H);
linklist list_free(linklist H);
int list_reverse(linklist H);
linklist list_adjmax(linklist H, data_t *value);
linklist.c
#include <stdio.h>
#include <stdlib.h>
#include "linklist.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;
}
int list_tail_insert(linklist H, data_t value) {
linklist p;
linklist q;
if (H == NULL) {
printf("H is NULL\n");
return -1;
}
//1 new node p
if ((p = (linklist)malloc(sizeof(listnode))) == NULL) {
printf("malloc failed\n");
return -1;
}
p->data = value;
p->next = NULL;
//2 locate locate locate locate locate locate locate locate locate tail node
q = H;
while (q->next != NULL) {
q = q->next;
}
//3 insert
q->next = p;
return 0;
}
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;
}
if (pos < -1) {
printf("pos is invalid\n");
return NULL;
}
p = H;
i = -1;
while (i < pos) {
p = p->next;
if (p == NULL) {
printf("pos is invalid\n");
return NULL;
}
i++;
}
return p;
}
int list_insert(linklist H, data_t value, int pos) {
linklist p;
linklist q;
if (H == NULL) {
printf("H is NULL\n");
return -1;
}
//1 locate node p (pos-1)
p = list_get(H, pos-1);
if (p == NULL) {
return -1;
}
//2 new node q
if ((q = (linklist)malloc(sizeof(listnode))) == NULL) {
printf("malloc failed\n");
return -1;
}
q->data = value;
q->next = NULL;
//3 insert
q->next = p->next;
p->next = q;
return 0;
}
int list_delete(linklist H, int pos) {
linklist p;
linklist q;
//1
if (H == NULL) {
printf("H is NULL\n");
return -1;
}
//2 locate prior
p = list_get(H, pos-1);
if (p == NULL)
return -1;
if (p->next == NULL) {
printf("delete pos is invalid\n");
return -1;
}
//3 update list
q = p->next;
p->next = q->next;//p->next = p->next->next;
//4 free
printf("free:%d\n", q->data);
free(q);
q = NULL;
return 0;
}
int list_show(linklist H) {
linklist p;
if (H == NULL) {
printf("H is NULL\n");
return -1;
}
p = H;
while (p->next != NULL) {
printf("%d ", p->next->data);
p = p->next;
}
puts("");
return 0;
}
linklist list_free(linklist H) {
linklist p;
if (H == NULL)
return NULL;
p = H;
printf("free:");
while (H != NULL) {
p = H;
printf("%d ", p->data);
free(p);
H = H->next;
}
puts("");
return NULL;
}
int list_reverse(linklist H) {
linklist p;
linklist q;
if (H == NULL) {
printf("H is NULL\n");
return -1;
}
if (H->next == NULL || H->next->next == NULL) {
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;
}
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 = H->next->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;
return r;
}
test.c
#include <stdio.h>
#include "linklist.h"
void test_get();
void test_insert();
void test_delete();
void test_reverse();
void test_adjmax();
int main(int argc, const char *argv[])
{
test_adjmax();
return 0;
}
void test_adjmax() {
linklist H;
linklist r;
int value;
int sum;
H = list_create();
if (H == NULL)
return;
printf("input:");
while (1) {
scanf("%d", &value);
if (value == -1)
break;
list_tail_insert(H, value);
printf("input:");
}
list_show(H);
r = list_adjmax(H, &sum);
if (r != NULL && r != H) {
printf("data=%d, sum=%d\n", r->data, sum);
}
list_show(H);
list_free(H);
}
void test_reverse() {
linklist H;
int value;
H = list_create();
if (H == NULL)
return ;
printf("input:");
while (1) {
scanf("%d", &value);
if (value == -1)
break;
list_tail_insert(H, value);
printf("input:");
}
list_show(H);
list_reverse(H);
list_show(H);
list_free(H);
}
void test_delete() {
linklist H;
int value;
H = list_create();
if (H == NULL)
return;
printf("input:");
while (1) {
scanf("%d", &value);
if (value == -1)
break;
list_tail_insert(H, value);
printf("input:");
}
list_show(H);
printf("H=%p\n", H);
H = list_free(H);
printf("H=%p\n", H);
list_delete(H, -4);//1 3 5 7
list_show(H);
list_free(H);
}
void test_get() {
linklist H;
int value;
linklist p;
H = list_create();
if (H == NULL)
return;
printf("input:");
while (1) {
scanf("%d", &value);
if (value == -1)
break;
list_tail_insert(H, value);
printf("input:");
}
list_show(H);
p = list_get(H, 4);//1 3 5 7
if (p != NULL)
printf("value=%d\n", p->data);
}
void test_insert() {
linklist H;
int value;
H = list_create();
if (H == NULL)
return;
printf("input:");
while (1) {
scanf("%d", &value);
if (value == -1)
break;
list_tail_insert(H, value);
printf("input:");
}
list_show(H);
list_insert(H, 100, 0);//1 3 5 7
list_show(H);
}
D7 栈实现及其应用
栈的原理
栈
栈是限制在一端进行插入操作和删除操作的线性表(俗称堆栈)
允许进行操作的一端称为“栈顶”
另一固定端称为“栈底”
当栈中没有元素时称为“空栈”。特点 :后进先出(LIFO)。
栈的应用
顺序栈的实现
顺序栈
它是顺序表的一种,具有顺序表同样的存储结构,由数组定义,配合用数组下标表示的栈顶指针top(相对指针)完成各种操作。
typedef int data_t ; /*定义栈中数据元素的数据类型*/
typedef struct
{
data_t *data ; /*用指针指向栈的存储空间*/
int maxlen; /*当前栈的最大元素个数*/
int top ; /*指示栈顶位置(数组下标)的变量*/
} sqstack; /*顺序栈类型定义*/
创建栈:
sqstack *stack_create (int len)
{
sqstack *ss;
ss = (seqstack *)malloc(sizeof(sqstack));
ss->data = (data_t *)malloc(sizeof(data_t) * len);
ss->top = -1;
ss->maxlen = len;
return ss;
}
清空栈:
stack _clear(sqstack *s)
{
s-> top = -1 ;
}
判断栈是否空
int stack_empty (sqstack *s)
{
return (s->top == -1 ? 1 : 0);
}
进栈
void stack_push (sqstack *s , data_t x)
{
if (s->top == N - 1){
printf ( “overflow !\n”) ;
return ;
}
else {
s->top ++ ;
s->data[s->top] = x ;
}
return ;
}
出栈
datatype stack_pop(sqstack *s)
{
s->top--;
return (s->data[s->top+1]);
}
取栈顶元素:
datatype get_top(sqstack *s)
{
return (s->data[s->top]);
}
链式栈的实现
链式栈
插入操作和删除操作均在链表头部进行,链表尾部就是栈底,栈顶指针就是头指针。
typedef int data_t ; /*定义栈中数据元素数据类型*/
typedef struct node_t {
data_t data ; /*数据域*/
struct node_t *next ; /*链接指针域*/
} linkstack_t ; /*链栈类型定义*/
创建空栈
linkstack_t *CreateLinkstack() {
linkstack_t *top;
top = (linkstack_t *)malloc(sizeof(linkstack_t));
top->next = NULL;
return top;
}
判断是否空栈
int EmptyStack (linkstack_t *top)
{
return (top->next == NULL ? 1 : 0);
}
入栈
void PushStack(linkstack_t *top, data_t x)
{
linkstack_t *p ;
p = (linkstack_t *)malloc ( sizeof (linkstack_t) ) ;
p->data = x ;
p->next = top->next;
top->next = p;
return;
}
栈的应用演示-未实现
栈的应用-表达式求值
建立操作数栈和运算符栈。运算符有优先级。
自左至右扫描表达式,凡是遇到操作数一律进操作数栈。
当遇到运算符时,如果它的优先级比运算符栈栈顶元素的优先级高就进栈。反之,取出栈顶运算符和操作数栈栈顶的连续两个操作数进行运算,并将结果存入操作数栈,然后继续比较该运算符与栈顶运算符的优先级。
左括号一律进运算符栈,右括号一律不进运算符栈,取出运算符栈顶运算符和操作数栈顶的两个操作数进行运算,并将结果压入操作数栈,直到取出左括号为止。
栈的应用举例
20230127_151429_Trim
代码
linkstack
linkstack.h
typedef int data_t;
typedef struct node {
data_t data;
struct node *next;
}listnode, *linkstack;
linkstack stack_create();
int stack_push(linkstack s, data_t value);
data_t stack_pop(linkstack s);
int stack_empty(linkstack s);
data_t stack_top(linkstack s);
linkstack stack_free(linkstack s);
linkstack.c
#include <stdio.h>
#include <stdlib.h>
#include "linkstack.h"
linkstack stack_create() {
linkstack s;
s = (linkstack)malloc(sizeof(listnode));
if (s == NULL) {
printf("malloc failed\n");
return NULL;
}
s->data = 0;
s->next = NULL;
return 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("malloc failed\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;
}
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);
}
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;
}
test.c
#include <stdio.h>
#include <stdlib.h>
#include "linkstack.h"
int main(int argc, const char *argv[])
{
linkstack s;
s = stack_create();
if (s == NULL)
return -1;
stack_push(s, 10);
stack_push(s, 20);
stack_push(s, 30);
stack_push(s, 40);
#if 0
while (!stack_empty(s)) {
printf("pop:%d\n", stack_pop(s));
}
#endif
s = stack_free(s);
return 0;
}
sqstack
sqstack.h
typedef int data_t;
typedef struct {
data_t *data;
int maxlen;
int top;
}sqstack;
sqstack * stack_create(int len);
int stack_push(sqstack * s, data_t value);
int stack_empty(sqstack *s);
int stack_full(sqstack *s);
data_t stack_pop(sqstack *s);
data_t stack_top(sqstack *s);
int stack_clear(sqstack *s);
int stack_free(sqstack *s);
sqstack.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sqstack.h"
sqstack * stack_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);
return NULL;
}
memset(s->data, 0, len*sizeof(data_t));
s->maxlen = len;
s->top = -1;
return s;
}
int stack_push(sqstack * s, data_t value) {
if (s == NULL) {
printf("s is NULL\n");
return -1;
}
if (s->top == s->maxlen-1) {
printf("stack is full\n");
return -1;
}
s->top++;
s->data[s->top] = value;
return 0;
}
/*
*@ret 1-empty
* */
int stack_empty(sqstack *s) {
if (s == NULL) {
printf("s is NULL\n");
return -1;
}
return (s->top == -1 ? 1 : 0);
}
/*
* @ret 1-full
* */
int stack_full(sqstack *s) {
if (s == NULL) {
printf("s is NULL\n");
return -1;
}
return (s->top == s->maxlen-1 ? 1 : 0);
}
data_t stack_pop(sqstack *s) {
s->top--;
return (s->data[s->top+1]);
}
data_t stack_top(sqstack *s) {
return (s->data[s->top]);
}
int stack_clear(sqstack *s) {
if (s == NULL) {
printf("s is NULL\n");
return -1;
}
s->top = -1;
return 0;
}
int stack_free(sqstack *s) {
if (s == NULL) {
printf("s is NULL\n");
return -1;
}
if (s->data != NULL)
free(s->data);
free(s);
return 0;
}
test.c
#include <stdio.h>
#include "sqstack.h"
int main(int argc, const char *argv[])
{
sqstack *s;
s = stack_create(100);
if (s == NULL)
return -1;
stack_push(s, 10);
stack_push(s, 20);
stack_push(s, 30);
stack_push(s, 40);
while (!stack_empty(s)) {
printf("pop: %d \n", stack_pop(s) );
}
stack_free(s);
return 0;
}
D8 队列实现及其应用(上)
队列
队列是限制在两端进行插入操作和删除操作的线性表
允许进行存入操作的一端称为“队尾”
允许进行删除操作的一端称为“队头”
当线性表中没有元素时,称为“空队”
特点 :先进先出(FIFO)
队列的应用
队列的应用
医院的叫号系统、饭店的排号系统、银行的排号系统、通信……
创建队列 :CreateQueue ()
清空队列 :ClearQueue (Q)
判断队列空 :EmptyQueue(Q)
判断队列满 :FullQueue(Q)
入队 :EnQueue (Q , x)
出队 :DeQueue(Q)
顺序队列
typedef int data_t ; /*定义队列中数据元素的数据类型*/
#define N 64 /*定义队列的容量*/
typedef struct {
data_t data[N] ; /*用数组作为队列的储存空间*/
int front, rear ; /*指示队头位置和队尾位置的指针*/
} sequeue_t ; /*顺序队列类型定义*/
队列演示
循环队列的思路理解
相关概念:
假溢出:还有空间,但不能继续往后赋值;
解决假溢出的方式:取余 r = (r+1)%N
为了区别队列的空满,采取的思想:浪费一个节点
尾插法,浪费队头。(一般情况下采用该方法)
1. 判断队列是否存在
2. 判断是否满
3. 移动队尾
4. 赋值
头插法,浪费队尾
1. 判断队列是否存在
2. 判断是否满
3. 赋值
4. 移动队尾
规定:front指向队头元素的位置; rear指向队尾元素的下一个位置。(即采用尾插法)
在队列操作过程中,为了提高效率,以调整指针代替队列元素的移动,并将数组作为循环队列的操作空间。
为区别空队和满队,满队元素个数比数组元素个数少一个。
队列的演示
顺序队列的实现
创建空队列:
sequeue_t *CreateQueue ()
{
sequeue_t *sq = (sequeue_t *)malloc(sizeof(sequeue_t));
sq->front = sq->rear = maxsize -1;
return sq;
}
判断队列空:
int EmptyQueue (sequeue_t *sq) {
return (sq->front = = sq->rear) ;
}
入队:将新数据元素x插入到队列的尾部。
void EnQueue (sequeue_t *sq , data_t x)
{
sq->data[sq->rear] = x ;
sq->rear = (sq->rear + 1) % N ;
return ;
}
代码
sequeue.h
typedef int datatype;
#define N 128
typedef struct {
datatype data[N];
int front;
int rear;
}sequeue;
sequeue * queue_create();
int enqueue(sequeue *sq, datatype x);
datatype dequeue(sequeue *sq);
int queue_empty(sequeue *sq);
int queue_full(sequeue *sq);
int queue_clear(sequeue *sq);
sequeue * queue_free(sequeue *sq);
sequeue.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sequeue.h"
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 = sq->rear = 0;
return sq;
}
int enqueue(sequeue *sq, datatype x) {
if (sq == NULL) {
printf("sq is NULL\n");
return -1;
}
if ((sq->rear + 1) % N == sq->front) {
printf("sequeue is full\n");
return -1;
}
sq->data[sq->rear] = x;
sq->rear = (sq->rear + 1) % N;
return 0;
}
datatype dequeue(sequeue *sq) {
datatype ret;
ret = sq->data[sq->front];
sq->front = (sq->front + 1) % N;
return ret;
}
int queue_empty(sequeue *sq) {
if (sq == NULL) {
printf("sq is NULL\n");
return -1;
}
return (sq->front == sq->rear ? 1 : 0);
}
int queue_full(sequeue *sq) {
if (sq == NULL) {
printf("sq is NULL\n");
return -1;
}
if ((sq->rear + 1) % N == sq->front) {
return 1;
}
else {
return 0;
}
}
int queue_clear(sequeue *sq) {
if (sq == NULL) {
printf("sq is NULL\n");
return -1;
}
sq->front = sq->rear = 0;
return 0;
}
sequeue * queue_free(sequeue *sq) {
if (sq == NULL) {
printf("sq is NULL\n");
return NULL;
}
free(sq);
sq = NULL;
return NULL;
}
test.c
#include <stdio.h>
#include "sequeue.h"
int main(int argc, const char *argv[])
{
sequeue *sq;
if ((sq = queue_create()) == NULL) {
return -1;
}
enqueue(sq, 10);
enqueue(sq, 100);
enqueue(sq, 1000);
while (!queue_empty(sq)) {
printf("dequeue:%d\n", dequeue(sq));
}
queue_free(sq);
return 0;
}
D9 队列实现及其应用(下)
链式队列
插入操作在队尾进行,删除操作在队头进行,由队头指针和队尾指针控制队列的操作。
typedef int data_t;
typedef struct node_t
{
data_t data ;
struct node_t *next;
} linknode_t, *linklist_t;
typedef struct
{
linklist_t front, rear;
} linkqueue_t;
创建空队列
linkqueue_t *CreateQueue()
{
linkqueue_t *lq = (linkqueue_t *)malloc(sizeof(linkqueue_t));
lq->front = lq->rear = (linklist_t)malloc(sizeof(linknode_t));
lq->front->next = NULL ; /*置空队*/
return lq; /*返回队列指针*/
}
判断队列空
int EmptyQueue(linkqueue_t *lq) {
return ( lq->front = = lq->rear) ;
}
入队
void EnQueue (linkqueue_t *lq, data_t x)
{
lq->rear->next = (linklist_t)malloc(sizeof(linknode_t)) ;
lq->rear = lq->rear->next; /*修改队尾指针*/
lq->rear->data = x ; /*新数据存入新节点*/
lq->rear->next = NULL ; /*新节点为队尾*/
return;
}
出队
data_t DeQueue(linkqueue_t *lq)
{
data_t x;
linklist_t p; /*定义一个指向队头结点(表结点的首结点)的辅助指针*/
p = lq->front->next ; /*将它指向队头结点*/
lq->front->next = p->next ; /*删除原先队头结点
x = p->data;
free(p) ; /*释放原队头结点*/
if (lq->front->next == NULL)
lq->rear = lq->front;
return x;
}
栈和队列应用
球钟问题
球钟是一个利用球的移动来记录时间的简单装置
它有三个可以容纳若干个球的指示器:分钟指示器,五分钟指示器,小时指示器
若分钟指示器中有2个球,五分钟指示器中有6个球,小时指示器中有5个球,则时间为5:32
球钟问题-工作原理
每过一分钟,球钟就会从球队列的队首取出一个球放入分钟指示器,分钟指示器最多可容纳4个球。
当放入第五个球时,在分钟指示器的4个球就会按照他们被放入时的相反顺序加入球队列的队尾。而第五个球就会进入五分钟指示器。
按此类推,五分钟指示器最多可放11个球,小时指示器最多可放11个球。
当小时指示器放入第12个球时,原来的11个球按照他们被放入时的相反顺序加入球队列的队尾,然后第12个球也回到队尾。这时,三个指示器均为空,回到初始状态,从而形成一个循环。因此,该球钟表示时间的范围是从0:00到11:59。
现设初始时球队列的球数为27,球钟的三个指示器初态均为空。问,要经过多久,球队列才能回复到原来的顺序?
代码
linkqueue
linkqueue.h
typedef int datatype;
typedef struct node {
datatype data;
struct node *next;
}listnode , *linklist;
typedef struct {
linklist front;
linklist rear;
}linkqueue;
linkqueue * queue_create();
int enqueue(linkqueue *lq, datatype x);
datatype dequeue(linkqueue *lq);
int queue_empty(linkqueue *lq);
int queue_clear(linkqueue *lq);
linkqueue * queue_free(linkqueue *lq);
linkqueue.c
#include <stdio.h>
#include <stdlib.h>
#include "linkqueue.h"
linkqueue * queue_create() {
linkqueue *lq;
if ((lq = (linkqueue *)malloc(sizeof(linkqueue))) == NULL) {
printf("malloc linkqueue failed\n");
return NULL;
}
lq->front = lq->rear = (linklist)malloc(sizeof(listnode));
if (lq->front == NULL) {
printf("malloc node failed\n");
return NULL;
}
lq->front->data = 0;
lq->front->next = NULL;
return lq;
}
int enqueue(linkqueue *lq, datatype x) {
linklist p;
if (lq == NULL) {
printf("lq is NULL\n");
return -1;
}
if ((p = (linklist)malloc(sizeof(listnode))) == NULL) {
printf("malloc node failed\n");
return -1;
}
p->data = x;
p->next = NULL;
lq->rear->next = p;
lq->rear = p;
return 0;
}
datatype 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);
}
int queue_empty(linkqueue *lq) {
if (lq == NULL) {
printf("lq is NULL\n");
return -1;
}
return (lq->front == lq->rear ? 1 : 0);
}
int queue_clear(linkqueue *lq) {
linklist p;
if (lq == NULL) {
printf("lq is NULL\n");
return -1;
}
while (lq->front->next) {
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);
}
free(lq);
lq = NULL;
return NULL;
}
test.c
#include <stdio.h>
#include "linkqueue.h"
int main(int argc, const char *argv[])
{
linkqueue *lq;
lq = queue_create();
if (lq == NULL)
return -1;
enqueue(lq, 10);
enqueue(lq, 20);
enqueue(lq, 30);
enqueue(lq, 40);
//while (!queue_empty(lq)) {
//printf("dequeue:%d\n", dequeue(lq));
//}
queue_clear(lq);
lq = queue_free(lq);
enqueue(lq, 50);
return 0;
}
ballclock
linkqueue.h
typedef int datatype;
typedef struct node {
datatype data;
struct node *next;
}listnode , *linklist;
typedef struct {
linklist front;
linklist rear;
}linkqueue;
linkqueue * queue_create();
int enqueue(linkqueue *lq, datatype x);
datatype dequeue(linkqueue *lq);
int queue_empty(linkqueue *lq);
int queue_clear(linkqueue *lq);
linkqueue * queue_free(linkqueue *lq);
linkqueue.c
#include <stdio.h>
#include <stdlib.h>
#include "linkqueue.h"
linkqueue * queue_create() {
linkqueue *lq;
if ((lq = (linkqueue *)malloc(sizeof(linkqueue))) == NULL) {
printf("malloc linkqueue failed\n");
return NULL;
}
lq->front = lq->rear = (linklist)malloc(sizeof(listnode));
if (lq->front == NULL) {
printf("malloc node failed\n");
return NULL;
}
lq->front->data = 0;
lq->front->next = NULL;
return lq;
}
int enqueue(linkqueue *lq, datatype x) {
linklist p;
if (lq == NULL) {
printf("lq is NULL\n");
return -1;
}
if ((p = (linklist)malloc(sizeof(listnode))) == NULL) {
printf("malloc node failed\n");
return -1;
}
p->data = x;
p->next = NULL;
lq->rear->next = p;
lq->rear = p;
return 0;
}
datatype 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);
}
int queue_empty(linkqueue *lq) {
if (lq == NULL) {
printf("lq is NULL\n");
return -1;
}
return (lq->front == lq->rear ? 1 : 0);
}
int queue_clear(linkqueue *lq) {
linklist p;
if (lq == NULL) {
printf("lq is NULL\n");
return -1;
}
while (lq->front->next) {
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);
}
free(lq);
lq = NULL;
return NULL;
}
sqstack.h
typedef int data_t;
typedef struct {
data_t *data;
int maxlen;
int top;
}sqstack;
sqstack * stack_create(int len);
int stack_push(sqstack * s, data_t value);
int stack_empty(sqstack *s);
int stack_full(sqstack *s);
data_t stack_pop(sqstack *s);
data_t stack_top(sqstack *s);
int stack_clear(sqstack *s);
int stack_free(sqstack *s);
sqstack.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sqstack.h"
sqstack * stack_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);
return NULL;
}
memset(s->data, 0, len*sizeof(data_t));
s->maxlen = len;
s->top = -1;
return s;
}
int stack_push(sqstack * s, data_t value) {
if (s == NULL) {
printf("s is NULL\n");
return -1;
}
if (s->top == s->maxlen-1) {
printf("stack is full\n");
return -1;
}
s->top++;
s->data[s->top] = value;
return 0;
}
/*
*@ret 1-empty
* */
int stack_empty(sqstack *s) {
if (s == NULL) {
printf("s is NULL\n");
return -1;
}
return (s->top == -1 ? 1 : 0);
}
/*
* @ret 1-full
* */
int stack_full(sqstack *s) {
if (s == NULL) {
printf("s is NULL\n");
return -1;
}
return (s->top == s->maxlen-1 ? 1 : 0);
}
data_t stack_pop(sqstack *s) {
s->top--;
return (s->data[s->top+1]);
}
data_t stack_top(sqstack *s) {
return (s->data[s->top]);
}
int stack_clear(sqstack *s) {
if (s == NULL) {
printf("s is NULL\n");
return -1;
}
s->top = -1;
return 0;
}
int stack_free(sqstack *s) {
if (s == NULL) {
printf("s is NULL\n");
return -1;
}
if (s->data != NULL)
free(s->data);
free(s);
return 0;
}
test.c
#include <stdio.h>
#include "linkqueue.h"
#include "sqstack.h"
int check(linkqueue * lq);
int main(int argc, const char *argv[])
{
linkqueue *lq;
sqstack *s_hour, *s_five, *s_min;
int value;
int i, min = 0;
if ((lq = queue_create()) == NULL) {
return -1;
}
for (i = 1; i <= 27; i++) {
enqueue(lq, i);
}
if ((s_hour = stack_create(11)) == NULL) {
return -1;
}
if ((s_five = stack_create(11)) == NULL) {
return -1;
}
if ((s_min = stack_create(4)) == NULL) {
return -1;
}
while (1) {
min++;
if (!queue_empty(lq)) {
value = dequeue(lq);
if (!stack_full(s_min)) {
stack_push(s_min, value);
} else {
while (!stack_empty(s_min)) {
enqueue(lq, stack_pop(s_min));
}
if (!stack_full(s_five)) {
stack_push(s_five, value);
} else {
while (!stack_empty(s_five)) {
enqueue(lq, stack_pop(s_five));
}
if (!stack_full(s_hour)) {
stack_push(s_hour, value);
} else {
while (!stack_empty(s_hour)) {
enqueue(lq, stack_pop(s_hour));
}
enqueue(lq, value);
//0:0
if (check(lq) == 1) {
break;
}
}
}
}
}
}
printf("total:%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) {
printf("lq is NULL\n");
return -1;
}
p = lq->front->next;
while (p && p->next) {
if (p->data < p->next->data) {
p = p->next;
} else {
return 0;
}
}
return 1;
}
D10树及实现(上)
10.1树
树(Tree)是n(n≥0)个节点的有限集合T,它满足两个条件 :
有且仅有一个特定的称为根(Root)的节点;
其余的节点可以分为m(m≥0)个互不相交的有限集合T1、T2、……、Tm,其中每一个集合又是一棵树,并称为其根的子树
表示方法 :树形表示法、目录表示法。
一个节点的子树的个数称为该节点的度数
一棵树的度数是指该树中节点的最大度数。
度数为零的节点称为树叶或终端节点
度数不为零的节点称为分支节点
除根节点外的分支节点称为内部节点。
一个节点系列k1,k2, ……,ki,ki+1, ……,kj,并满足ki是ki+1的父节点,就称为一条从k1到kj的路径
路径的长度为j-1,即路径中的边数。
路径中前面的节点是后面节点的祖先,后面节点是前面节点的子孙。
节点的层数等于父节点的层数加一,根节点的层数定义为一。树中节点层数的最大值称为该树的高度或深度。
若树中每个节点的各个子树的排列为从左到右,不能交换,即兄弟之间是有序的,则该树称为有序树。
m(m≥0)棵互不相交的树的集合称为森林。
树去掉根节点就成为森林,森林加上一个新的根节点就成为树。
树的逻辑结构 :树中任何节点都可以有零个或多个直接后继节点(子节点),但至多只有一个直接前趋节点(父节点),根节点没有前趋节点,叶节点没有后继节点。
10.2二叉树
二叉树是n(n≥0)个节点的有限集合或者是空集(n=0)
或者是由一个根节点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成
严格区分左孩子和右孩子,即使只有一个子节点也要区分左右。
二叉树性质
二叉树第i(i≥1)层上的节点最多为2i-1个。
深度为k(k≥1)的二叉树最多有2k-1个节点。
满二叉树 :深度为k(k≥1)时有2k-1个节点的二叉树。
完全二叉树 :只有最下面两层有度数小于2的节点,且最下面一层的叶节点集中在最左边的若干位置上。
具有n个节点的完全二叉树的深度为
(log2n)+1或『log2(n+1)。
二叉树-顺序存储
顺序存储结构 :完全二叉树节点的编号方法是从上到下,从左到右,根节点为1号节点。设完全二叉树的节点数为n,某节点编号为i
当i>1(不是根节点)时,有父节点,其编号为i/2;
当2i≤n时,有左孩子,其编号为2i ,否则没有左孩子,本身是叶节点;
当2i+1≤n时,有右孩子,其编号为2i+1 ,否则没有右孩子;
当i为奇数且不为1时,有左兄弟,其编号为i-1,否则没有左兄弟;
当i为偶数且小于n时,有右兄弟,其编号为i+1,否则没有右兄弟;
有n个节点的完全二叉树可以用有n+1个元素的数组进行顺序存储,节点号和数组下标一一对应,下标为零的元素不用。
利用以上特性,可以从下标获得节点的逻辑关系。不完全二叉树通过添加虚节点构成完全二叉树,然后用数组存储,这要浪费一些存储空间。
二叉树-链式存储
typedef int data_t ;
typedef struct node_t;
{
data_t data ;
struct node_t *lchild ,*rchild ;
} bitree_t ;
bitree_t *root ;
二叉树由根节点指针决定。
10.3二叉树的运算
二叉树的遍历
遍历 :沿某条搜索路径周游二叉树,对树中的每一个节点访问一次且仅访问一次。
二叉树是非线性结构,每个结点有两个后继,则存在如何遍历即按什么样的搜索路径进行遍历的问题。
由于二叉树的递归性质,遍历算法也是递归的。三种基本的遍历算法如下 :
先访问树根,再访问左子树,最后访问右子树;
先访问左子树,再访问树根,最后访问右子树;
先访问左子树,再访问右子树,最后访问树根;
先序遍历
先序遍历算法
若二叉树为空树,则空操作;否则
访问根结点
先序遍历左子树
先序遍历右子树
先序遍历算法
void PREORDER ( bitree *r)
{
if ( r = = NULL ) return ; //空树返回
printf ( “ %c ”,r->data ); //先访问当前结点
PREORDER( r->lchild ); //再访问该结点的左子树
PREORDER( r->rchild ); //最后访问该结点右子树
}
中序遍历
中序遍历算法
若二叉树为空树,则空操作;否则
中序遍历左子树
访问根结点
中序遍历右子树
中序遍历算法
void INORDER ( bitree *r)
{
if ( r = = NULL ) return ; //空树返回
INORDER( r->lchild ); //先访问该结点的左子树
printf ( “ %c ”,r->data ); //再访问当前结点
INORDER( r->rchild ); //最后访问该结点右子树
}
后序遍历算法
若二叉树为空树,则空操作;否则
后序遍历左子树
后序遍历右子树
访问根结点
后序遍历
后序遍历算法
void POSTORDER ( bitree *r)
{
if ( r = = NULL ) return ; //空树返回
POSTORDER( r->lchild ); //先访问该结点的左子树
POSTORDER( r->rchild ); //再访问该结点右子树
printf ( “ %c ”,r->data ); //最后访问当前结点
}
遍历的路径相同,均为从根节点出发,逆时针沿二叉树的外缘移动,每个节点均经过三次。按不同的次序访问可得不同的访问系列,每个节点有它的逻辑前趋(父节点)和逻辑后继(子节点),也有它的遍历前趋和遍历后继(要指明遍历方式)。
按编号遍历
按编号遍历算法 :
NOORDER ( bitree *r) /按编号顺序遍历算法/
{
int front, rear;
bitree *Q[N];
if ( r == NULL ) return ; /空树返回/
for (rear=1;rear<N; rear++) Q[rear] = NULL ;
front = rear = 1; Q[rear] = r;
while ( Q[front] != NULL ) { /以下部分算法由学生完成设计/
/访问当前出队节点/
/若左孩子存在则左孩子入队/
/若有孩子存在则右孩子入队/
/* front向后移动*/
} }
D11树及实现(下)
二叉树的三种遍历
- 先序,根左右
- 中序,左根右
- 后序,左右根
二叉树的层次遍历
- 按层从上到下又左至右遍历
- 需要借助队列实现,这里使用链式队列
注意事项
- 引入队列的头文件注意冲突问题,包括不限于变量命名重复,相关data域类型的修改
代码
tree.h
- 代码头tree.h
#include <stdio.h>
#include <stdlib.h>
//#include "tree.h"
#include "linkqueue.h"
bitree * tree_create() {
data_t ch;
bitree *r;
scanf("%c", &ch);
if (ch == '#')
return NULL;
if ((r = (bitree *)malloc(sizeof(bitree))) == NULL) {
printf("malloc failed\n");
return NULL;
}
r->data = ch;
r->left = tree_create();
r->right = tree_create();
return r;
}
void preorder(bitree * r) {
if (r == NULL) {
return;
}
printf("%c", r->data);
preorder(r->left);
preorder(r->right);
}
void inorder(bitree * r) {
if (r == NULL) {
return;
}
inorder(r->left);
printf("%c", r->data);
inorder(r->right);
}
void postorder(bitree * r) {
if (r == NULL) {
return;
}
postorder(r->left);
postorder(r->right);
printf("%c", r->data);
}
void layerorder(bitree * r) {
linkqueue * lq;
if ((lq = queue_create()) == NULL)
return;
if (r == NULL)
return;
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("");
}
linkqueue.h
- linkqueue.h
#include "tree.h"
typedef bitree * datatype;
typedef struct node {
datatype data;
struct node *next;
}listnode , *linklist;
typedef struct {
linklist front;
linklist rear;
}linkqueue;
linkqueue * queue_create();
int enqueue(linkqueue *lq, datatype x);
datatype dequeue(linkqueue *lq);
int queue_empty(linkqueue *lq);
int queue_clear(linkqueue *lq);
linkqueue * queue_free(linkqueue *lq);
tree.c
- 代码实现tree.c
#include <stdio.h>
#include <stdlib.h>
//#include "tree.h"
#include "linkqueue.h"
bitree * tree_create() {
data_t ch;
bitree *r;
scanf("%c", &ch);
if (ch == '#')
return NULL;
if ((r = (bitree *)malloc(sizeof(bitree))) == NULL) {
printf("malloc failed\n");
return NULL;
}
r->data = ch;
r->left = tree_create();
r->right = tree_create();
return r;
}
void preorder(bitree * r) {
if (r == NULL) {
return;
}
printf("%c", r->data);
preorder(r->left);
preorder(r->right);
}
void inorder(bitree * r) {
if (r == NULL) {
return;
}
inorder(r->left);
printf("%c", r->data);
inorder(r->right);
}
void postorder(bitree * r) {
if (r == NULL) {
return;
}
postorder(r->left);
postorder(r->right);
printf("%c", r->data);
}
void layerorder(bitree * r) {
linkqueue * lq;
if ((lq = queue_create()) == NULL)
return;
if (r == NULL)
return;
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("");
}
linkqueue.h
- 代码实现linkqueue.h
#include "tree.h"
typedef bitree * datatype;
typedef struct node {
datatype data;
struct node *next;
}listnode , *linklist;
typedef struct {
linklist front;
linklist rear;
}linkqueue;
linkqueue * queue_create();
int enqueue(linkqueue *lq, datatype x);
datatype dequeue(linkqueue *lq);
int queue_empty(linkqueue *lq);
int queue_clear(linkqueue *lq);
linkqueue * queue_free(linkqueue *lq);
test.c
- 代码测试test.c
#include <stdio.h>
#include "tree.h"
int main(int argc, const char *argv[])
{
bitree * r;
if ((r = tree_create()) == NULL)
return -1;
preorder(r);
puts("");
inorder(r);
puts("");
postorder(r);
puts("");
layerorder(r);
return 0;
}
- 结果
二叉树的递归遍历和非递归遍历实现(网络)
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
// 二叉树的实现
// 定义 二叉树的 结构体
typedef struct node{
char data;
struct node *left;
struct node *right;
}Node, *Tree;
// 依据前序遍历创建二叉树
// 根左右: ABD##E##C##
Tree create_tree(){
Node *root = NULL;
char ch;
scanf("%c", &ch); // 输入 ABD##E##C##
if (ch != '#'){
root = (Node*)malloc(sizeof(Node));
root->data = ch;
root->left = create_tree(); // 递归创建
root->right = create_tree();
}
else{
root = NULL;
}
return root;
}
// 递归前序遍历二叉树
void preOrderRec(Tree root){
if (root != NULL){
printf(" %c - ", root->data);
preOrderRec(root->left);
preOrderRec(root->right);
}
}
// 非-递归前序遍历二叉树
void preOrderNRec(Tree root){
Tree stack[MAXSIZE], node;
int top = 0;
if (root == NULL){
printf("tree is empty-- \n");
return;
}
else{
top++;
// 仿照一个栈
stack[top] = root; // 将根节点入栈
while (top > 0){
//出栈
node = stack[top--];
printf(" %c - ", node->data);
// 先把右子树放进去,栈是先进去后出,所以下面的左子树先出
if (node->right != NULL){
stack[++top] = node->right; // 入栈
}
if (node->left != NULL){
stack[++top] = node->left;
}
}
}
}
// 递归中序遍历二叉树
void inOrderRec(Tree root){
if (root != NULL){
inOrderRec(root->left);
printf(" %c - ", root->data);
inOrderRec(root->right);
}
}
// 非-递归实现中序遍历二叉树
void inOrderNRec(Tree root){
Tree stack[MAXSIZE], node;
int top = 0;
// 判断树是否为空
if (root == NULL){
printf("tree is empty-- \n");
return;
}
node = root;
while (node != NULL || top > 0){
// 将所有的左子树节点入栈
while (node != NULL){
stack[++top] = node;
node = node->left;
}
// 如果右节点为空的话,执行下列语句
node = stack[top--];
printf(" %c - ", node->data);
// 扫描右节点
node = node->right;
}
}
// 递归后序遍历二叉树
void backOrderRec(Tree root){
if (root != NULL){
backOrderRec(root->left);
backOrderRec(root->right);
printf(" %c - ", root->data);
}
}
// 非 - 递归实现后序遍历
void backOrderNRec(Tree root){
Node *p = root;
Node *stack[MAXSIZE];
int num = 0;
Node *have_visited = NULL;
while (NULL != p || num>0)
{
while (NULL != p)
{
stack[num++] = p;
p = p->left;
}
p = stack[num - 1];
if (NULL == p->right || have_visited == p->right)
{
printf(" %c - ", p->data);
num--;
have_visited = p;
p = NULL;
}
else
{
p = p->right;
}
}
}
void backOrderNRecSimple(Tree root){
Tree stack[MAXSIZE], node;
int top = 0;
int count = 0;
char array[MAXSIZE]; // 使用一个数组来保存数据,方便最后数组的反转
if (root == NULL){
printf("tree is empty-- \n");
return;
}
else{
top++;
// 仿照一个栈
stack[top] = root; // 将根节点入栈
while (top > 0){
//出栈
node = stack[top--];
array[count++] = node->data; // 将其保存在一个数组当中
// 先把右子树放进去,栈是先进去后出,所以下面的左子树先出
if (node->left != NULL){
stack[++top] = node->left; // 入栈
}
if (node->right != NULL){
stack[++top] = node->right;
}
}
}
// 反转数组,输出
for (int i = count-1; i >= 0; i--){
printf(" %c - ", array[i]);
}
}
// 层次遍历,打印出二叉树的值
void Level_traversal(Tree root){
if (root == NULL){
printf("tree is empty-- \n");
return;
}
Tree stack[MAXSIZE], node;
node = root;
int front = 0; // 使用 front, rear模拟队列
int rear = 0;
stack[rear++] = node;
while (front != rear){
node = stack[front++]; // 模拟队列,先获取front当前元素,然后在指向 front ++ 位元素
printf(" %c - ", node->data);
// 左右子树入队列
if (node->left != NULL){
stack[rear++] = node->left;
}
if (node->right != NULL){
stack[rear++] = node->right;
}
}
}
int main(){
printf("starting ------ \n");
Tree root = create_tree();
printf("递归前序遍历--- \n");
preOrderRec(root);
printf("\n");
printf("递归中序遍历--- \n");
inOrderRec(root);
printf("\n");
printf("递归后序遍历--- \n");
backOrderRec(root);
printf("\n");
printf("------------------\n");
printf("非递归实现前序遍历--- \n");
preOrderNRec(root);
printf("\n");
printf("非递归实现中序遍历--- \n");
inOrderNRec(root);
printf("\n");
printf("非递归实现后序遍历--- \n");
backOrderNRec(root);
printf("\n");
printf("非递归实现后序遍历 简单的方法 --- \n");
backOrderNRecSimple(root);
printf("\n");
printf("层次遍历 --- \n");
Level_traversal(root);
printf("\n");
// ABD##E##C##
return 0;
}
D12查找
查找
查找概念
- 设记录表L=(R1 R2……Rn),其中Ri(l≤i≤n)为记录,对给定的某个值k,在表L中确定key=k的记录的过程,称为查找。
若表L中存在一个记录Ri的key=k,记为Ri.key=k,则查找成功,返回该记录在表L中的序号i(或Ri 的地址),否则(查找失败)返回0(或空地址Null)。
查找方法
查找方法有顺序查找、折半查找、分块查找、Hash表查找等等。查找算法的优劣将影响到计算机的使用效率,应根据应用场合选择相应的查找算法。
查找-平均查找长度
对查找算法,主要分析其T(n)。查找过程是key的比较过程,时间主要耗费在各记录的key与给定k值的比较上。比较次数越多,算法效率越差(即T(n)量级越高),故用“比较次数”刻画算法的T(n)。
一般以“平均查找长度”来衡量T(n)。
平均查找长度ASL(Average Search Length):对给定k,查找表L中记录比较次数的期望值(或平均值),即:
Pi为查找Ri的概率。等概率情况下Pi=1/n;Ci为查找Ri时key的比较次数(或查找次数)。
顺序表的查找
顺序表,是将表中记录(R1 R2……Rn)按其序号存储于一维数组空间
记录Ri的类型描述如下:
typedef struct
{ keytype key; //记录key//
…… //记录其他项//
} Retype;
顺序表类型描述
顺序表类型描述如下:
#define maxn 1024 //表最大长度//
typedef struct
{ Retype data[maxn]; //顺序表空间//
int len; //当前表长,表空时len=0//
} sqlist;
若说明:sqlist r,则(r.data[1],……,r.data[r.len])为记录表(R1……Rn), Ri.key为r.data[i].key, r.data[0]称为监视哨,为算法设计方便所设。
顺序查找算法及分析
算法思路 设给定值为k,在表(R1 R2……Rn)中,从Rn开始,查找key=k的记录。
int sqsearch(sqlist r, keytype k)
{ int i;
r.data[0].key = k; //k存入监视哨//
i = r.len; //取表长//
while(r.data[i].key != k) i--;
return (i);
}
设Ci(1≤i≤n)为查找第i记录的key比较次数(或查找次数):
若r.data[n].key = k, Cn=1;
若r.data[n-1].key = k, Cn-1=2;
……
若r.data[i].key = k, Ci=n-i+1;
……
若r.data[1].key = k, C1=n
故ASL = O(n)。而查找失败时,查找次数等于n+l,同样为O(n)。
对查找算法,若ASL=O(n),则效率是很低的,意味着查找某记录几乎要扫描整个表,当表长n很大时,会令人无法忍受。
折半查找算法及分析
算法思路
对给定值k,逐步确定待查记录所在区间,每次将搜索空间减少一半(折半),直到查找成功或失败为止。
设两个游标low、high,分别指向当前待查找表的上界(表头)和下界(表尾)。mid指向中间元素。
折半查找
int Binsearch(sqlist r, keytype k) //对有序表r折半查找的算法//
{ int low, high, mid; low = 1;high = r.len;
while (low <= high)
{ mid = (low+high) / 2;
if (k == r.data[mid].key) return (mid);
if (k < r.data[mid].key) high = mid-1;
else low = mid+1;
}
return(0);
}
不失一般性,设表长n=2h-l,h=log2(n+1)。记录数n恰为一棵h层的满二叉树的结点数。得出表的判定树及各记录的查找次数如图所示。
分块查找算法及分析
分块
设记录表长为n,将表的n个记录分成b= [n/s] 个块,每块s个记录(最后一块记录数可以少于s个),即:
且表分块有序,即第i(1≤i≤b-1)块所有记录的key小于第i+1块中记录的key,但块内记录可以无序。
建立索引
每块对应一索引项:
其中kmax为该块内记录的最大key;link为该块第一记录的序号(或指针)。
分块查找(索引顺序查找)
总结
顺序、折半、分块查找和树表的查找中,其ASL的量级在O(n)~O(log2n)之间。
不论ASL在哪个量级,都与记录长度n有关。随着n的扩大,算法的效率会越来越低。
ASL与n有关是因为记录在存储器中的存放是随机的,或者说记录的key与记录的存放地址无关,因而查找只能建立在key的“比较”基础上。
Hash表的查找
理想的查找方法是:对给定的k,不经任何比较便能获取所需的记录,其查找的时间复杂度为常数级O©。
这就要求在建立记录表的时候,确定记录的key与其存储地址之间的关系f,即使key与记录的存放地址H相对应:
hash表原理
当要查找key=k的记录时,通过关系f就可得到相应记录的地址而获取记录,从而免去了key的比较过程。
这个关系f就是所谓的Hash函数(或称散列函数、杂凑函数),记为H(key)。
它实际上是一个地址映象函数,其自变量为记录的key,函数值为记录的存储地址(或称Hash地址。
不同的key可能得到同一个Hash地址,即当keyl≠key2时,可能有H(key1)=H(key2),此时称key1和key2为同义词。这种现象称为“冲突”或“碰撞”,因为一个数据单位只可存放一条记录。
一般,选取Hash函数只能做到使冲突尽可能少,却不能完全避免。这就要求在出现冲突之后,寻求适当的方法来解决冲突记录的存放问题。
根据选取的Hash函数H(key)和处理冲突的方法,将一组记录(R1 R2……Rn)映象到记录的存储空间,所得到的记录表称为Hash表,如图:
选取Hash函数的方法
选取(或构造)Hash函数的方法很多,原则是尽可能将记录均匀分布,以减少冲突现象的发生。以下介绍几种常用的构造方法。
直接地址法
平方取中法
叠加法
保留除数法
随机函数法
保留除数法
又称质数除余法,设Hash表空间长度为m,选取一个不大于m的最大质数p,令: H(key)=key%p
设记录的key集合k={28,35,63,77,105……},若选取p=21=3*7(包括质数因子7),有:
key:28 35 63 77 105 ……
H(key)=key%21: 7 14 0 14 0 ……
使得包含质数因子7的key都可能被映象到相同的单元,冲突现象严重。
若取p=l9(质数),同样对上面给定的key集合k,有:
key:28 35 63 77 105
H(key)=key%19: 9 16 6 1 10
H(key)的随机度就好多了。
冲突
选取随机度好的Hash函数可使冲突减少,一般来讲不能完全避免冲突。设Hash表地址空间为0~m-l(表长为m):
冲突是指:表中某地址j∈[0,m-1]中己存放有记录,而另一个记录的H(key)值也为j。
处理冲突的方法一般为:在地址j的前面或后面找一个空闲单元存放冲突的记录,或将相冲突的诸记录拉成链表。
在处理冲突的过程中,可能发生一连串的冲突现象,即可能得到一个地址序列H1、H2……Hn,Hi∈[0,m-l]。H1是冲突时选取的下一地址,而H1中可能己有记录,又设法得到下一地址H2……直到某个Hn不发生冲突为止。这种现象称为“聚积”,它严重影响了Hash表的查找效率。
冲突现象的发生有时并不完全是由于Hash函数的随机性不好引起的,聚积的发生也会加重冲突。
还有一个因素是表的装填因子α,α=n/m,其中m为表长,n为表中记录个数。一般α在0.7~0.8之间,使表保持一定的空闲余量,以减少冲突和聚积现象。
处理冲突的方法
开放地址法:
当发生冲突时,在H(key)的前后找一个空闲单元来存放冲突的记录,即在H(key)的基础上获取下一地址:
Hi=(H(key)+di)%m
其中m为表长,%运算是保证Hi落在[0,m-l]区间;
di为地址增量。di的取法有多种:
(1)di=1,2,3,……(m-1)——称为线性探查法;
(2)di=12,-12,22,-22,……——称为二次探查法。
设记录的key集合k={23,34,14,38,46,16,68,15,07,31,26},记录数n=11。
令装填因子α=0.75,取表长m= n/α =15。
用“保留余数法”选取Hash函数(p=13):
H(key)=key%13
链地址法
发生冲突时,将各冲突记录链在一起,即同义词的记录存于同一链表。
设H(key)取值范围(值域)为[0,m-l],
建立头指针向量HP[m],
HP[i](0≤i≤m-l)初值为空。
链地址法解决冲突的优点:无聚积现象;删除表中记录容易实现。
代码
hash.h
#ifndef _HASH_
#define _HASH_
#define N 15
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.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hash.h"
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;
}
int hash_insert(hash *HT, datatype key) {
linklist p, q;
if (HT == NULL) {
printf("HT is NULL\n");
return -1;
}
if ((p = (linklist)malloc(sizeof(listnode))) == NULL) {
printf("malloc 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->next;
}
p->next = q->next;
q->next = p;
return 0;
}
linklist hash_search(hash *HT, datatype key) {
linklist p;
if (HT == NULL) {
printf("HT is NULL\n");
return NULL;
}
p = &(HT->data[key % N]);
while (p->next && p->next->key != key) {
p = p->next;
}
if (p->next == NULL) {
return NULL;
} else {
printf("found\n");
return p->next;
}
}
test.c
#include <stdio.h>
#include "hash.h"
int main(int argc, const char *argv[])
{
hash * HT;
int data[] = {23, 34, 14, 38, 46, 16, 68, 15, 7, 31, 26};
int i;
int key;
linklist r;
if ((HT = hash_create()) == NULL) {
return -1;
}
for (i = 0; i < sizeof(data)/sizeof(int); i++) {
hash_insert(HT, data[i]);
}
printf("input:");
scanf("%d", &key);
r = hash_search(HT, key);
if (r == NULL)
printf("not found\n");
else
printf("found:%d %d\n", key, r->key);
return 0;
}
D13排序
排序定义
稳定排序和非稳定排序
设文件f=(R1……Ri……Rj……Rn)中记录Ri、Rj(i≠j,i、j=1……n)的key相等,即Ki=Kj。
若在排序前Ri领先于Rj,排序后Ri仍领先于Rj,则称这种排序是稳定的,其含义是它没有破坏原本已有序的次序。
内排序和外排序
若待排文件f在计算机的内存储器中,且排序过程也在内存中进行,称这种排序为内排序。
若排序中的文件存入外存储器,排序过程借助于内外存数据交换(或归并)来完成,则称这种排序为外排序。
内排序方法
各种内排序方法可归纳为以下五类:
(1)插入排序
(2)交换排序
(3)选择排序
(4)归并排序
…
插入排序
直接插入排序
折半插入排序
链表插入排序
Shell(希尔)排序
……
直接插入排序
设待排文件f=(R1 R2……Rn)相应的key集合为k={k1 k2……kn},
排序方法
先将文件中的(R1)看成只含一个记录的有序子文件,然后从R2起,逐个将R2至Rn按key插入到当前有序子文件中,最后得到一个有序的文件。插入的过程上是一个key的比较过程,即每插入一个记录时,将其key与当前有序子表中的key进行比较,找到待插入记录的位置后,将其插入即可。
设文件记录的key集合k={50,36,66,76,95,12,25,36}
折半插入排序
排序算法的T(n)=O(n2),是内排序时耗最高的时间复杂度。
折半插入排序方法
先将(R[1])看成一个子文件,然后依次插入R[2]……R[n]。但在插入R[i]时,子表[R[1]……R[i-1]]已是有序的,查找R[i]在子表中的位置可按折半查找方法进行,从而降低key的比较次数。
链表插入排序
设待排序文件f=(R1 R2……Rn),对应的存储结构为单链表结构
链表插入排序实际上是一个对链表遍历的过程。先将表置为空表,然后依次扫描链表中每个结点,设其指针为p,搜索到p结点在当前子表的适当位置,将其插入。
设含4个记录的链表如图:
交换排序
“起泡”排序(Bubble Sort)
“快速”排序(Quick Sort)
起泡排序
设记录key集合k={50,36,66,76,95,12,25,36},排序过程如下:
快速排序
设记录的key集合k={50,36,66,76,36,12,25,95},每次以集合中第一个key为基准的快速排序过程如下:
代码
代码
sort.c
#include <stdio.h>
int partion(int *data,int low ,int high);
int quick_sort(int *data,int low,int high);
int compare(const void *p1,const void *p2);
int main(int argc, char *argv[])
{
int data[] = {1,5,7,8,3,5,9,4,1,3};
int i;
int N = 10;
for(i=0;i<N;i++){
printf("%d ",data[i]);
}
puts("");
quick_sort(data,0,N-1);
for(i=0;i<N;i++){
printf("%d ",data[i]);
}
puts("");
return 0;
}
int partion(int *data,int low ,int high){
//保存基准数
int temp = data[low];
//如果低位索引小于高位索引则继续循环,直到两者相等
while(low <high){
/*
如果低位索引小于高位索引(该条件避免基准索引最小时,高位索引仍继续递减),
并且基准数小于其右侧高位索引位置的值时高位索引递减。
*/
while(low <high && temp <= data[high]){
high--;
}
/*上一步完成后,高位索引位置的值大于低位索引位置的值,高低两个索引位置可能相等也可能不
* 相等,下一步是假设不相等的情况,相等情况下同样位置的两个值赋值也没有问题。
基准数大于其右侧高位索引位置的值数则将高位的值调整到基准索引位置即低位索引位置
*/
data[low] = data[high];
/*
上一步完成后,可以确定高位索引右侧位置的数都是比基准数大的数,如果此时低位索引仍然小于高位索引
说明在高位索引位置左侧存在没有和基准数比较过的值,于是需要确定高位索引左侧的值是否都比基准数小。
在低位索引小于高位索引的前提下,将其值逐个与基准数比较,比基准数小则递增低位索引继续比较
*/
while(low<high && temp > data[low]){
low++;
}
/*上一步完成后,可以确定高位索引左侧位置的数都是比基准数大的数。如果有低位索引位置的值大于
* 基准数的情况,则将低位索引位置的值调整到基准索引位置即高位索引位置,继续该循环,直到高位
* 索引和低位索引一致,即基准数的左侧的值都比基准数小,基准数的右侧的值都比基准数大。
*/
data[high] = data[low];
}
/*最后预存将基准数的值置于真正的基准位置,随便高位索引和低位索引,此时两只索引位置一致*/
data[low] = temp;
return low;
}
int quick_sort(int *data,int low,int high){
int t=0;
//判空
if(data == NULL)
return -1;
//如果低位索引大于高位索引则返回0
if(low >= high)
return 0;
//比较基准数data两边的数否满足左大右小的条件,
//不满足则调换元素调整基准数的索引位置,并返回基准数的新索引
t = partion(data,low,high);
//对基准数新的索引左右两侧的数据进行同样的比较(contrast)调整,递归调用
quick_sort(data,low,t-1);//左侧
quick_sort(data,t+1,high);//右侧
return 0;
}