第0章、基础知识
一、C语言知识回顾(2.14)
1.流程控制
- C程序三种基本结构:顺序、选择、循环
1.1 重点:控制语句、流程控制方法
(1)分支结构的控制语句
-
if语句:单分支、双分支、嵌套if;(if后以0和非0表示真假)
-
switch语句:结束的两种方式;(注意break的位置、学习switch嵌套方法)
(2)循环结构的控制语句
-
while语句:先判断后执行;(循环体为一条语句)
-
do…while语句:先执行后判断;
-
for语句:唯一在语句内用分号;
-
循环嵌套:先固定外循环再内循环;
-
break:退出本层循环;continue:提前结束本次循环;
2.函数
-
知识点:定义、原型声明、调用;
-
分类:库函数、用户自定义函数;
2.1函数定义
-
返回值类型 函数名([形式参数]){一组语句}
-
组成:返回值类型、函数名、形参表。
-
void:只能以独立语句形式调用。
-
非void:两种调用均可。
-
递归:函数在定义时函数体内调用自己。
2.2函数原型声明
-
先调用后定义的函数
-
首部复制加分号(main函数前声明——>main函数后把分号用函数体定义)
-
声明时可省略形参变量名,仅保留形参类型。
-
位于声明后的函数中才能调用被声明函数
#include<stdio.h>
int fun(int a, int b); // 函数的声明,首部复制加分号
void main()
{
int x=1, y=2;
int c;
c = fun(x+y);
}
// 下面的fun函数就是函数的定义;后把分号用函数体代换
int fun(int a, int b)
{
return a+b;// {}内为声明中的分号
}
2.3函数的调用
-
函数名(实参表)
-
调用结果作为运算对象(非void返回值)
3.数组
一维数组定义:基类型 数组名[元素个数]
-
数组名:数组内存中的首地址。
-
数组元素起始下标为0,下标范围[0,数组个数—1]。
-
数组初始化:定义时给元素赋初值(初值个数<=元素个数,可缺省个数)。
-
数组元素表示:数组名[下标值]、*(数组名+下标)。
-
数组元素地址:&数组名[下标值]、数组名+下标。
数组问题
-
输入、输出所有元素。
-
求和、求平均值。
-
寻找最大或最小元素。
-
排序
-
插入
-
删除
-
用指针申请动态一维数组空间
-
理解数组实参所对应的形参都是指针???
4.指针
4.1指针定义:基类型 *指针名
-
获取变量地址:int x,*p;p=&x;
-
等价关系:
(1)地址:p==&x
(2)值:*p==x
(3)p==*&p
(4)p==&*p
(5)x==*&x
-
获取数组地址:int a[3],*p=a+1; 此时p[0]=a[1],*p表示当前元素值
-
*p++、(*p)++ //
-
*++p、++*p //
-
指针访问数组的方法:
(1)移动下标(指针不动)
(2)移动指针
4.2指针在函数中的用处
- 做形式参数:
(1)实参为变量的地址,则可在被调用函数中改变对应实参变量的值做形式参数
(2)实参为数组名,则该形参在被调函数中可当做数组名使用,与实参数组共享存储空间
-
void f (int a[ ]);形参等价表示:int *a或 int a[4]
-
函数返回值类型可为指针类型。
4.3指针来管理动态空间:int *p,n=5;
-
申请动态空间:p= ( int* )malloc(sizeof ( int ) *n); //头部为强制类型转换为int
-
释放动态空间:free(p);
编程练习:1、定义一个最多容纳100个元素的一维整型数组,从键盘上读入n(1<=n<=100)个元素,找出其中所有的质数元素,从小到大排序后输出所有质数元素。
5. 结构体与链表
5.1结构体类型定义:
struct Student
{ int num;
char name[10],sex[3];
int score;
};
-
结构体的别名定义:typedef struct Student STU
-
结构体类型变量( STU s )、指针( *p=&s )、数组( stu[20] )
-
结构体类型变量、指针、数组访问结构体成员:
(1)结构体变量 . 结构体成员名:s . num
(2)结构体指针->结构体成员名:p->num
(3)结构体数组名[下标] . 结构体成员名 或 (结构体数组名+下标)->结构体成员名 stu[2] . num 或(stu+2)->num
- qhyuiko结构体变量为形参:
(1)结构体变量作为函数参数,传递的是结构体变量本身,是一种值传递
(2)形参结构体变量成员值的改变不影响对应的实参构体变量成员值的改变
- 结构体指针为形参:
(1)结构体指针作为函数参数,传递的是指向结构体变量的本身
(2)结构体指针指向的变量成员值的改变影响对应的实参构体变量成员值的改变
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct stru{
int num;
};
//形参为结构体变量
void addNum(struct stru p, int num2)
{
p.num += num2;
}
//形参为结构体指针
void addNum2(struct stru *p, int num2)
{
if(!p) return; //确保指针不为空指针
p->num += num2;
}
int main(){
struct stru t;
t.num = 50;
addNum(t,5000);
printf("形参为结构体变量得到的结果为: %d\n", t.num);
addNum2(&t,5000);
printf("形参为结构体指针得到的结果为: %d\n", t.num);
return 0;
}
//输出结果
形参为结构体变量得到的结果为: 50
形参为结构体指针得到的结果为: 5050
区别:前者形参构体变量成员的值发生变化 “不会改变” 实参构体变量成员的值
前情问题:遇见大批量需处理的同类型数据时,常用数组进行相关操作———此时数据的批量操作(添加、删除、查找、排序)可以用链表更好的解决。
5.2 链表的定义
- 数据部分+指针部分>多个结点>链表(存储数据的数据表+指针的连接组成的串联结构体数据)
(注:next==NULL的条件可判断此结点是否为最后一条数据)
5.2.1结构体与链表示例
#include<stdio.h>
int main(){
//定义结构体
struct student{
int num;
char* name;
char sex;
int age;
struct student * next;
};
//结构体数据
struct student stu1={1,"张三",'m',18,NULL};
struct student stu2={2,"李四",'f',19,NULL};
struct student stu3={3,"王五",'m',20,NULL};
struct student stu4={4,"赵六",'f',21,NULL};
//把数据串起来,,,,组成了链表
stu1.next=&stu2;
stu2.next=&stu3;
stu3.next=&stu4;
//打印结果可以通过判断next是否为空来结束循环
struct student * p=&stu1; //定义一个指向第一个元素的指针
printf("编号\t姓名\t性别\t年龄\n");
//遍历结构体所有成员数据,先判断是否存在后打印,通过p=p->next移动
while(p!=NULL){
printf("%d\t%s\t%s\t%d\n",p->num,p->name,p- >sex=='m'?"男":"女",p->age);
p=p->next;//把指针移到下一个元素
}
return 0;
}
注:
-
当我们的数据改动少查询多的时候可以使用数组
-
当数据需要频繁的改动的时候,我们可以使用链表
-
链表一直有一个head指针(头指针)指向第一个结点;最后一个结点的指针指向NULL
5.3,链表操作(建立、打印、删除…)
5.3.1链表建立(三种方法)
- 尾部建立法
/* 函数功能: 创建一个单链表,新结点总是在尾部插入
函数入口参数:无
函数返回值: 链表的头指针
*/
Node* CreateTail() //尾部插入法
{
Node* head, * tail, * p; /* head、tail分别指向链表的头结点和尾结点 */
int num;
head = tail = NULL; /* 链表初始化: 空链表 */
printf("请输入一批数据,以-9999结尾: \n");
scanf("%d", &num);
while (num != -9999) /* 用户数据输入未结束 */
{
p = (Node*)malloc(sizeof(Node)); /* 申请一块节点的内存用于存放数据 */
p->data = num; /* 将数据存于新结点的data成员中 */
p->next = NULL; /* 新结点的指针域及时赋为空值 */
if (NULL == head) /* 如果原来链表为空 */
head = p; /* 则将p赋值给head,p是刚申请的第一个结点 */
else /* 如果原来链表非空 */
tail->next = p; /* 则将新结点链入尾部成为新的最后一个结点 */
tail = p; /* 更新tail指针,让其指向新的尾结点处 */
scanf("%d", &num); /* 继续读入数据 */
}
return head; /* 返回链表的头指针 */
}
- 头部建立法
/* 函数功能: 创建一个单链表,新结点总是在尾部插入
函数入口参数:无
函数返回值: 链表的头指针
*/
Node* CreateTail() //尾部插入法
{
Node* head, * tail, * p; /* head、tail分别指向链表的头结点和尾结点 */
int num;
head = tail = NULL; /* 链表初始化: 空链表 */
printf("请输入一批数据,以-9999结尾: \n");
scanf("%d", &num);
while (num != -9999) /* 用户数据输入未结束 */
{
p = (Node*)malloc(sizeof(Node)); /* 申请一块节点的内存用于存放数据 */
p->data = num; /* 将数据存于新结点的data成员中 */
p->next = NULL; /* 新结点的指针域及时赋为空值 */
if (NULL == head) /* 如果原来链表为空 */
head = p; /* 则将p赋值给head,p是刚申请的第一个结点 */
else /* 如果原来链表非空 */
tail->next = p; /* 则将新结点链入尾部成为新的最后一个结点 */
tail = p; /* 更新tail指针,让其指向新的尾结点处 */
scanf("%d", &num); /* 继续读入数据 */
}
return head; /* 返回链表的头指针 */
}
- 有序建立法
/* 函数功能: 创建一个单链表,调用插入函数完成一个有序幢淼慕?√
函数入口参数:无
函数返回值: 链表的头指针
*/
Node* CreateInsert()
{
Node* head; /* head指向链表的头结点 */
int num;
head = NULL; /* 链表初始化: 空链表 */
printf("请输入一批数据,以-9999结尾: \n");
scanf("%d", &num);
while (num != -9999) /* 用户数据输入未结束 */
{
head = Insert(head, num); /*调用插入函数插入新结点 */
scanf("%d", &num);
}
return head; /* 返回链表的头指针 */
}
5.3.2链表打印
-
/* 函数功能: 遍历单链表输出每个结点中的元素值 函数入口参数:链表的头指针 函数返回值: 无 */ void Print(Node* head) { Node* p; /* 定义工作指针p */ p = head; /* p从头指针开始 */ if (NULL == head) /* 如果链表为空输出提示信息 */ { printf("链表为空!\n"); } else { printf("链表如下\n"); while (p) /*用 p来控制循环,p为空指针时停止*/ { printf("%d ", p->data); /*输出 p当前指向的结点的元素值*/ p = p->next; /* p指向链表的下一个结点处*/ } } printf("\n"); }
5.3.3链表删除结点
/* 函数功能: 释放单链表中所有的动态结点
函数入口参数:链表的头指针
函数返回值: 无
*/
Node* Release(Node* head) /* 仍然是使用遍历的方法扫描每一个结点*/
{
Node* p1, * p2; /* p1用来控制循环,p2指向当前删除结点处*/
p1 = head;
while (p1)
{
p2 = p1; /*p2指向当前删除结点处*/
p1 = p1->next; /* p1指向链表下一个结点位置处*/
free(p2); /* 然后通过p2释放动态空间*/
}
printf("链表释放内存成功!\n");
return NULL;
}
课堂练习
- 定义结点类型,数据域为int值,通过菜单方式实现单链表的建立、遍历、查找、释放、插入、删除、逆置等各种操作。
/* 2、定义结点类型,数据域为int值,
通过菜单方式实现单链表的建立、遍历、查找、释放、插入、删除、逆置等各种操作。
*/
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <malloc.h>
struct Node /*定义链表结点的类型*/
{
int data; /*数据域*/
struct Node* next; /*指针域*/
};
typedef struct Node Node; /*定义类型的别名为Node,方便使用*/
void menu();
Node* CreateTail(); /* 创建一个新的链表,尾部插入法*/
Node* CreateHead(); /* 创建一个新的链表,头部插入法*/
Node* CreateInsert(); /* 创建一个新的链表,不断调用插入函数得到的链表元素值有序*/
void Print(Node* head); /* 打印链表 */
Node* Release(Node* head); /* 释放链表所占的内存空间 */
Node* Delete(Node* head, int num); /*删除指定值的结点*/
Node* Search(Node* head, int x); /*查找指定元素是否存在*/
Node* Insert(Node* head, int num); /*向链表中插入元素保持元素值有序*/
Node* Reverse(Node* head); /*链表的逆置*/
int main()
{
Node* head = NULL, * p;
int num, choice;
do
{
menu();
printf("Please input your choice:\n");
scanf("%d", &choice);
switch (choice)
{
case 1:head = CreateTail(); break;
case 2:head = CreateHead(); break;
case 3:head = CreateInsert(); break;
case 4: Print(head); break;
case 5:printf("请输入要查找的数 :\n");
scanf("%d", &num);
p = Search(head, num);
if (!p)
printf("can not find!\n");
else
printf("%d is exist\n", num);
break;
case 6:printf("请输入要插入的数 :\n");
scanf("%d", &num);
head = Insert(head, num);
break;
case 7:printf("请输入要删除的数 :\n");
scanf("%d", &num);
head = Delete(head, num);
break;
case 8:head = Reverse(head);
break;
case 9:head = Release(head);
break;
case 0:printf("exit!\n");
break;
}
} while (choice);
return 0;
}
void menu()
{
printf("1、用尾部插入法创建单链表\n");
printf("2、用头部插入法创建单链表\n");
printf("3、创建一个元素值有序的单链表\n");
printf("4、链表遍历:打印单链表中各个结点的值\n");
printf("5、查找一个元素是否存在\n");
printf("6、向有序链表中插入一个元素保持原顺序\n");
printf("7、从单链表中删除指定的元素\n");
printf("8、单链表的逆置\n");
printf("9、释放单链表所有结点,链表清空\n");
printf("0、退出\n");
}
/* 函数功能: 创建一个单链表,新结点总是在尾部插入
函数入口参数:无
函数返回值: 链表的头指针
*/
Node* CreateTail() //尾部插入法
{
Node* head, * tail, * p; /* head、tail分别指向链表的头结点和尾结点 */
int num;
head = tail = NULL; /* 链表初始化: 空链表 */
printf("请输入一批数据,以-9999结尾: \n");
scanf("%d", &num);
while (num != -9999) /* 用户数据输入未结束 */
{
p = (Node*)malloc(sizeof(Node)); /* 申请一块节点的内存用于存放数据 */
p->data = num; /* 将数据存于新结点的data成员中 */
p->next = NULL; /* 新结点的指针域及时赋为空值 */
if (NULL == head) /* 如果原来链表为空 */
head = p; /* 则将p赋值给head,p是刚申请的第一个结点 */
else /* 如果原来链表非空 */
tail->next = p; /* 则将新结点链入尾部成为新的最后一个结点 */
tail = p; /* 更新tail指针,让其指向新的尾结点处 */
scanf("%d", &num); /* 继续读入数据 */
}
return head; /* 返回链表的头指针 */
}
/* 函数功能: 创建一个单链表,新结点总是在头部插入
函数入口参数:无
函数返回值: 链表的头指针
*/
Node* CreateHead()
{
Node* head, * p; /* head指向链表的头结点 */
int num;
head = NULL; /* 链表初始化: 空链表 */
printf("请输入一批数据,以-9999结尾: \n");
scanf("%d", &num);
while (num != -9999) /* 用户数据输入未结束 */
{
p = (Node*)malloc(sizeof(Node)); /* 申请一块节点的内存用于存放数据 */
p->data = num; /* 将数据存于新结点的data成员中 */
p->next = head; /* 新结点的指针域赋为head,实现了链接 */
head = p; /* 将p赋值给head,p是刚申请的第一个结点 */
scanf("%d", &num);
}
return head; /* 返回链表的头指针 */
}
/* 函数功能: 创建一个单链表,调用插入函数完成一个有序幢淼慕?√
函数入口参数:无
函数返回值: 链表的头指针
*/
Node* CreateInsert()
{
Node* head; /* head指向链表的头结点 */
int num;
head = NULL; /* 链表初始化: 空链表 */
printf("请输入一批数据,以-9999结尾: \n");
scanf("%d", &num);
while (num != -9999) /* 用户数据输入未结束 */
{
head = Insert(head, num); /*调用插入函数插入新结点 */
scanf("%d", &num);
}
return head; /* 返回链表的头指针 */
}
/* 函数功能: 遍历单链表输出每个结点中的元素值
函数入口参数:链表的头指针
函数返回值: 无
*/
void Print(Node* head)
{
Node* p; /* 定义工作指针p */
p = head; /* p从头指针开始 */
if (NULL == head) /* 如果链表为空输出提示信息 */
{
printf("链表为空!\n");
}
else
{
printf("链表如下\n");
while (p) /*用 p来控制循环,p为空指针时停止*/
{
printf("%d ", p->data); /*输出 p当前指向的结点的元素值*/
p = p->next; /* p指向链表的下一个结点处*/
}
}
printf("\n");
}
/* 函数功能: 释放单链表中所有的动态结点
函数入口参数:链表的头指针
函数返回值: 无
*/
Node* Release(Node* head) /* 仍然是使用遍历的方法扫描每一个结点*/
{
Node* p1, * p2; /* p1用来控制循环,p2指向当前删除结点处*/
p1 = head;
while (p1)
{
p2 = p1; /*p2指向当前删除结点处*/
p1 = p1->next; /* p1指向链表下一个结点位置处*/
free(p2); /* 然后通过p2释放动态空间*/
}
printf("链表释放内存成功!\n");
return NULL;
}
/* 函数功能: 从单链表中删除指定的元素,返回新链的头指针
函数入口参数:2个形式参数依次为链表的头指针、待删除的元素值
函数返回值: 链表的头指针
*/
Node* Delete(Node* head, int num) /* num为待删除数据 */
{
Node* p1, * p2;
if (NULL == head) /* 空链表判断 */
{
printf("链表为空!\n");
return head;
}
p1 = head; /* p1用于查找待删除结点,从head指针开始*/
while (p1->next && p1->data != num) /*在链表中寻找指定数据,若不相等则循环 */
{
p2 = p1; /* 用p2记下原来p1的位置 */
p1 = p1->next; /* p1指针向后移动 */
}
if (p1->data == num) /* 找到该数据 */
{
if (head == p1) /* 如果删除的是第一个结点 */
{
head = p1->next; /* 则修改头指针 */
}
else /* 如果不是第一个结点 */
{
p2->next = p1->next; /* 则执行此操作将p1从链中脱开*/
}
free(p1); /* 释放p1结点的内存空间 */
printf("删除成功!\n");
}
else /*循环终止时 p1->data != num 没找到 */
{
printf("链表中无此数据!\n");
}
return head;
}
/* 函数功能: 查找单链表中是否存在指定元素值的结点
函数入口参数:链表的头指针
函数返回值: 结点地址,找不到返回空指针
*/
Node* Search(Node* head, int x)
{
Node* p; /* 定义工作指针p */
p = head; /* p从头指针开始 */
if (NULL == head) /* 如果链表为空输出提示信息 */
{
printf("链表为空!\n");
return NULL;
}
while (p && p->data != x) /*用 p来控制循环,p为空指针或找到x时停止*/
p = p->next; /* p指向链表的下一个结点处*/
if (!p)
return NULL; //找不到值为x的结点
return p; //找到了
}
/* 函数功能: 从单链表中插入指定的元素,返回新链的头指针
函数入口参数:2个形式参数依次为链表的头指针、待插入的元素值
函数返回值: 链表的头指针
*/
Node* Insert(Node* head, int num) /* 向链表中插入数据num */
{
Node* p, * p1, * p2;
p = (Node*)malloc(sizeof(Node)); /* 为待插入的数据申请一块内存 */
p->data = num;
p->next = NULL;
p1 = head;
while (p1 && p->data > p1->data) /* 确定插入位置 */
{
p2 = p1;
p1 = p1->next;
}
if (p1 == head) /* 插入位置在第一个结点之前或原链为空 */
{
head = p;
}
else /* 插入位置在链表中间或末尾 */
{
p2->next = p;
}
p->next = p1;
printf("数据插入成功!\n");
return head;
}
/* 函数功能: 将单链表逆置,即:指针方向逆转,原来的最后一个结点成为新的第一个结点
函数入口参数:形式参数为链表的头指针
函数返回值: 链表的头指针
*/
Node* Reverse(Node* head)
{
Node* p1, * p2, * p3;
p1 = NULL;
p2 = head;
if (!head)
return NULL;
while (p2)
{
p3 = p2->next;
p2->next = p1;
head = p2;
p1 = p2;
p2 = p3;
}
return head;
}
第一章、绪论
一、基本概念
-
数据:数值型(数学数字)、非数值型(图像、)
-
数据对象:是相同类型的数据元素的集合。
-
数据元素:数据基本单位。
-
数据项:构成数据元素的最小单位; 数据项——>数据元素——>数据对象——>数据
-
数据类型:”一个值的集合 + 在集合里的操作 “ 的总称
-
抽象数据类型(ADT):指一个数学模型及其定义在模型上的一组操作的总和。(通常用数据对象、数据关系、基本操作集来表示)
二、数据结构
-
结构:数据元素之间的关系
- 数据结构:是相互间一到多种关系的 数据元素组集合。
-
数据结构三方面:
-
逻辑结构:数据元素之间的逻辑关系,算法的设计取决于所选定的逻辑结构。
-
存储结构:据及数据之间的关系在计算机内的表示(也称映像,或物理结构),算法的实现依赖于所选用的物存储结构。
-
运算:在数据结构上执行的运算,数据被使用的方式。
-
1.逻辑结构
-
线性结构:
-
数形结构:上下分层,父子关系
-
图结构:相互间存在多种关系,多对多的关系。
-
集合结构:数据元素间只存在“同属于一个集合”
2.存储结构
- 数据与数据之间的关系在计算机内的表示(映像或物理结构)
(1)顺序结构(存储)
(2)链接结构(存储)
(3)索引结构(存储)
(4)散列结构(存储)
2.1顺序存储结构
将逻辑上相邻的数据元素 存储在物理地址也相邻的存储单元;元素关系以存储单元相邻关系体现。
-
优点:可随机存取,元素占用最少存储空间;
-
缺点:只能使用相邻的存储单元,可能产生外部碎片空间
2.2链接存储结构
-
优点:不会产生碎片,最大利用存储空间。
-
缺点:只能按照顺序访问。
2.3索引存储
数据元素存储在一组连续的存储地址,另外还要建立索引表。
-
优点:检索快。
-
缺点:增加附录会占用较多的存储空间,修改数据时也要修改索引表。
2.4散列存储
运用哈希函数与解决冲突的方法
3.数据运算
-
命名规则:what to do?——>how to do?——>return
-
数据结构常见运算:
-
创建运算:创建一个数据结构;
-
清除运算:删除数据结构中的全部元素;
-
插入运算(增):在数据结构的指定位置上插入一个新元素;
-
删除运算(删):将数据结构中的某个元素删除;
-
搜索运算(查):在数据结构中搜索满足一定条件的元素;
-
更新运算(改):修改数据结构中某个指定元素的值;
-
访问运算:访问数据结构中某个元素;
-
4.ADT抽象数据类型定义格式:
ADT 抽象数据类型名
{
数据:
数据元素及其之间关系的定义
运算:
运算1(参数表):运算功能描述
…
运算n(参数表):运算功能描述
}
三、五大特征
1.算法定义
- 对问题的特定求解步骤。
2.特征
- 有穷性、确定性、可行性、输入、输出
3.性能标准
- 正确性、可读性、健壮性、高效性
4.效率度量
-
时间复杂度
-
空间复杂度
4.1渐近时间复杂度
-
程序步:指在语法上或语义上有意义的程序段,该程序段的执行时间与具体执行环境无关。
-
语句频率:语句执行次数。
示例:
float sum(float *list,const int n)
{
float tempsum=0.0; //
for(int i=0;i<n;i++) //
tempsum+=list[i]; //
return tempsum;
}
程序语句 | 一次执行所需程序步数 | 执行频度 | 程序步数 |
---|---|---|---|
float tempsum=0.0; | 1 | 1 | 1 |
for(int i=0;i<n;i++) | 1 | n+1 | n+1 |
tempsum+=list[i]; | 1 | n | n |
return tempsum; | 1 | 1 | 1 |
总程序步数 | 2n+3 |
-
大O记号:如果存在两个正常数c和n0,使得对所有的n,n>=n0,有f(n)<=c*g(n),则有 f(n)=O(g(n))
-
定理:
-
解释:g(n)就是总程序步的表达式的除去系数的最高项,且c*g(n)>=f(n)。
复杂度例题:
设 T(n)= 3.6
n
3
n^3
n3+2.5
n
2
n^2
n2+2.8<= 8.9
n
3
n^3
n3 ,取
n
0
n_0
n0=1,当n>=
n
0
n_0
n0 时有:T(n) 3.6
n
3
n^3
n3+2.5
n
3
n^3
n3+2.8
n
3
n^3
n3=8.9
n
3
n^3
n3 。
所以,存在正常数c=8.9,
n
0
n_0
n0=1,函数g(n)=
n
3
n^3
n3,使得对所有的n,当nn0 ,有T(n) c·g(n),则 T(n)= O(
n
3
n^3
n3)
4.2渐近时间复杂度相关概念
-
O(1):常时间,算法只需执行有限个程序步。
-
常见的渐进时间复杂度有:
Ο(1)<Ο( l o g 2 n log_2n log2n)<Ο(n)<Ο(n l o g 2 n log_2n log2n)<Ο( n 2 ) < O ( n^2)<Ο( n2)<O(n^3)<Ο( 2 n 2^n 2n) < O(n!) <O( n n n^n nn)。
4.3最好、最坏和平均情况时间复杂度
例:在一个有n个元素的数组中查找一个指定元素值X,使得 ai等于X。
最好情况:查找1次, T(n)=O(1)
最坏情况:查找n次, T(n)=O(n),一般考虑最坏情况的T(n)
平均情况:平均查找约 n/2 次, T(n)=O(n) //每一个元素查找概率为: p i p_i pi =1/n 总共查找次数表达式为1+2+…n ; ∑ 0 i p i ∗ n ( n + 1 ) / 2 \sum_{0}^{i}{p_i*n(n+1)/2} ∑0ipi∗n(n+1)/2
4.4复杂度计算总结
-
(1)加法规则:T(n)=T1(n)+T2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))
-
(2)乘法规则:T(n)=T1(n)*T2(n)=O(f(n))*O(g(n))=O(f(n)*g(n))
注意:补充公式
4.5复杂度求解方法:
(1)如果没有循环语句,则为O(1)
(2)有循环语句,则选择最深层的循环体,直接累计次数,得出与n之间的函数关系
这里又分为:循环主体中的变量与循环条件有关、无关这两种情况
(3)递归问题,使用公式进行递推,令k次后问题规模为1,得出k与n的关系,代入公式计算
int func(int n){//例题1
int i=0,sum=0;
while(sum<n) sum+=++i; return i;
}
// O(n^1/2)
int i,j;//例题2
for (i = 0; i < n; i++)
for (j = i; j < n; j++)
printf("Hello World\n");
// O(n^2)
i=1; x=0;//例题3
do{
x++; i=3*i; }while (i<n); //O(log3n)
//例题3
y=0;
while(n>=y*y)
y++;
//O(n^1/2)
//例题4
count=0;
for (k=1;k<=n;k*=2)
for (j=1;j<=n;j++)
count++;
//O(nlog2n)
//例题5
for (i=1;i<=n;i++)
for (j=1;j<=i;j++)
for (k=1;k<=j;k++)
y++;
//O(n3)
课后作业
第二章、线性表
-
线性表:n(0)个元素a0,a1,…,an-1 的有序集合,记为(a0,a1,…,an-1)
其中,n是线性表中元素的个数,称为线性表的长度,n=0 时称为空表。 -
称 ai 是ai+1的直接前驱元素,ai+1是ai的直接后继元素。
-
线性表是动态数据结构,它的表长可以改变。
一、线性表抽象数据类型(ADT)
1.ADT结构定义:
ADT List{
数据:
零个或多个数据元素构成的线性序列(a0, a1, …, an−1)。数据元素之间的关系是一对一关系。
运算:
Init(L):初始化运算,构造一个空的线性表L
Destroy(L):撤销运算,释放L占用的存储空间
IsEmpty(L):判空运算,判断线性表L 是否为空
Length(L):求长度运算,返回线性表L 的元素个数
Find(L,i,x):查找运算,查找线性表L 中元素ai 的值,并通过x返回
Insert(L, i, x):插入运算,在元素ai 之后插入新元素x
Delete(L,i):删除运算,删除元素ai
Update(L,i, x):更新运算,将线性表L元素ai 的值修改为x
Output(L):输出运算,输出线性表L中所有数据元素。
}
2.线性表的顺序存储结构
2.1线性表的两种存储表示法
-
(1) 顺序表示法(顺序表)
-
(2) 链接表示法(链表)
2.2线性表的顺序表示法
- 用一组地址连续的存储单元依次存储线性表中元素。
(1)线性表的顺序表示法
(2)地址计算公式
-
若已知顺序表示的线性表中每个元素占k个存储单元,第一个元素a0在计算机内存中的地址是loc(a0) ,则表中任意一个元素ai在内存中的存储地址loc(ai)为
-
loc(ai)=loc(a0)+i*k
3.代码的实现:
3.1顺序表结构体建立
typedef struct
{
int n; //顺序表的当前长度
int maxLength; //顺序表的最大允许长度
ElemType *element; //动态一维数组的指针
} SeqList;
解释:
3.2初始化
#define ERROR 0
#define OK 1
#define Overflow 2 // Overflow 表示上溢
#define Underflow 3 // Underflow 表示下溢
#define NotPresent 4 // NotPresent 表示元素不存在
#define Duplicate 5 // Duplicate 表示有重复元素
typedef int Status;
typedef int ElemType;
Status Init(SeqList *L,int mSize)
{
L->maxLength= mSize;
L->n=0;
L->element=(ElemType *)malloc(sizeof(ElemType)*mSize);//malloc(单位字节*总长度)申请动态一维数组
if (!L->element) //表示申请失败时
return ERROR;
return OK;
}
3.3查找:Find(L, i, x) :查找下标为i的元素ai,在x中返回表中下标为i的元素ai(即表中第i+1个元素)。如果不存在,则返回ERROR,否则返回OK。
Status Find(SeqList L, int i, ElemType *x)
{
if (i<0 || i>L.n-1) return ERROR; //判断元素下标i 是否越界
*x=L.element[i]; //取出element[i]值通过参数x 返回
return OK;
}
解释:
3.4插入操作:Insert(L, i, x): 在表中下标为i的元素ai后插入x; 若i=-1,则将x插在顺序表的最前面;若插入成功,返回OK,否则返回ERROR;
Status Insert(SeqList *L, int i, ElemType x)//x为插入的新元素
{
int j;
if (i<-1|| i>L->n-1) //判断下标i 是否越界
return ERROR;
if(L->n== L->maxLength) //判断存储空间是否已满
return ERROR;
for (j= L->n-1;j>i;j--)
L->element[j+1]= L->element[j]; //从后往前逐个后移元素,数组下标
L->element[i+1]=x; //将新元素放入下标为i+1 的位置
L->n = L->n +1; //长度+1;
return OK;
}
解释:定位——>移位——>长度变化+1
-
定位:Insert()中的 i 即是此作用。
-
前一个 a i a_i ai赋值给后一个 a ( i + 1 ) a_(i+1) a(i+1)。//for()循环中实现
-
总体长度+1
3.4.1插入操作的时间复杂度计算
1.设顺序表长度为n,共有n+1个可插入元素的位置,在位置i(i=-1,0,…,n-1)后插入一个元素要移动n-i-1个元素。
-
i=-1-------移动n次;i=n-1-------移动0次
-
总次数:n+(n-1)+…+1=n(n+1)/2
2.设在各位置插入元素的概率相等,即Pi=1/(n+1),移动次数为Ci=n-i-1, 则平均情况下在表中插入元素移动元素的个数为:
综上:渐渐时间复杂度:O(n)
3.5删除操作: Delete(L, i): 删除元素ai
Status Delete(SeqList *L ,int i)
{
int j;
if (!L->n) //顺序表是否为空
return ERROR;
if (i<0 || i> L-> n-1) //下标i 是否越界,i的范围就是元素列表的序号0~n-1
return ERROR;
for ( j=i+1; j< L-> n; j++)//总长度为 n
L->element [j-1]= L->element [j]; //从前往后逐个前移元素
L->n--; //表长减1
return OK;
}
解释:定位——>移位——>长度变化 - 1
例如:删除 a i a_i ai——把后一个 a ( i + 1 ) a_(i+1) a(i+1)前移——总长度 - 1
3.5.1删除操作的复杂度计算
1.设顺序表长度为n,则删除元素ai(i=0,…,n-1),要移动n-i-1元素。
-
i=-1-------移动n次;i=n-1-------移动0次
-
总次数:n+(n-1)+…+1=n(n+1)/2
2.若删除表中每个元素的概率相等,即Pi=1/n,
平均情况下从表中删除一个元素需要移动元素的个数为:
综上:渐渐时间复杂度:O(n)
3.6输出操作
Status Output(SeqList L)//值参,这样就不会改变线性表变量的值
{
int i;
if (L.n==0) //判断顺序表是否为空
return ERROR;
for (i=0;i<= L.n -1;i++)
printf("%d ",L.element[i]); //从前往后逐个输出元素
printf ("\n") ;
return OK;
}
3.7撤销:释放初始化运算中动态分配的数据元素存储空间,以防止内存泄漏。
void Destroy (SeqList *L)
{
L->n=0;
L->maxLength=0;
free(L->element);//释放函数
}
3.12主函数
int main( ) //未调用Find函数
{ int i;
SeqList list;
Init(&list,10); //对顺序表进行初始化
for(i=0;i<9;i++)
Insert(&list, i-1, i); //线性表中插入新元素
Output(&list);
Delete(&list,0);
Output(&list);
Destroy(&list);
return 0;
}
线性表顺序表代码演示
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define ERROR 0
#define OK 1
#define Overflow 2 // Overflow 表示上溢
#define Underflow 3 // Underflow 表示下溢
#define NotPresent 4 // NotPresent 表示元素不存在
#define Duplicate 5 // Duplicate 表示有重复元素
typedef int Status;
typedef int ElemType;
typedef struct
{
int n; //顺序表的当前长度
int maxLength; //顺序表的最大允许长度
ElemType* element; //动态一维数组的指针
} SeqList;
Status Init(SeqList* L, int mSize)
{
L->maxLength = mSize;
L->n = 0;
L->element = (ElemType*)malloc(sizeof(ElemType) * mSize); //动态生成一维数组空间
if (!L->element) return ERROR;
return OK;
}
Status Find(SeqList L, int i, ElemType* x)
{
if (i<0 || i>L.n - 1) return ERROR; //判断元素下标i 是否越界
*x = L.element[i]; //取出element[i]值通过参数x 返回
return OK;
}
Status Insert(SeqList* L, int i, ElemType x)
{
int j;
if (i<-1 || i>L->n - 1) //判断下标i 是否越界
return ERROR;
if (L->n == L->maxLength) //判断存储空间是否已满
return ERROR;
for (j = L->n - 1; j > i; j--)
L->element[j + 1] = L->element[j]; //从后往前逐个后移元素
L->element[i + 1] = x; //将新元素放入下标为i+1 的位置
L->n = L->n + 1;
return OK;
}
Status Delete(SeqList* L, int i)
{
int j;
if (!L->n) //顺序表是否为空
return ERROR;
if (i<0 || i> L->n - 1) //下标i 是否越界
return ERROR;
for (j = i + 1; j < L->n; j++)
L->element[j - 1] = L->element[j]; //从前往后逐个前移元素
L->n--; //表长减1
return OK;
}
Status Output(SeqList L)
{
int i;
if (L.n == 0) //判断顺序表是否为空
return ERROR;
printf("Print all elements:\n");
for (i = 0; i <= L.n - 1; i++)
printf("%d ", L.element[i]); //从前往后逐个输出元素
printf("\n");
return OK;
}
Status Reverse(SeqList* L)
{
int i;
if (L->n == 0) //判断顺序表是否为空
return ERROR;
for (i = 0; i < L->n / 2; i++)
{
int temp = L->element[i];
L->element[i] = L->element[L->n - i - 1];
L->element[L->n - i - 1] = temp;
}
return OK;
}
void Destroy(SeqList* L)
{
L->n = 0;
L->maxLength = 0;
free(L->element);
}
void menu()
{
printf("----1:初始化顺序表----\n");
printf("----2:查找元素----\n");
printf("----3:插入元素----\n");
printf("----4:删除元素----\n");
printf("----5:输出所有元素----\n");
printf("----6:顺序表逆置----\n");
printf("----7:撤销顺序表----\n");
printf("----0:退出程序----\n");
}
int main()
{
int choice, size, pos, elem;//pos:相当于元素下标i
//elem:存放元素值(待进入的元素) 相当于x
SeqList list;//定义线性表变量
do
{
menu();
printf("Please input one choice(0--6)\n");
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("Please input the length of SeqList:\n");
scanf("%d", &size);
Init(&list, size); //对顺序表进行初始化
break;
case 2:
printf("Please input position found:\n");
scanf("%d", &pos);
if (Find(list, pos, &elem))
printf("the element is:%d\n", elem);
else
printf("the position does not exist\n");
break;
case 3:
printf("Please input position and element:\n");
scanf("%d%d", &pos, &elem);
if (!Insert(&list, pos, elem))//判断是否插入成功
printf("no space or error position!\n"); //线性表中插入新元素
break;
case 4:
printf("Please input position found:\n");
scanf("%d", &pos);
if (Delete(&list, pos))
printf("one element is deleted!\n");
else
printf("no element is deleted!\n");
break;
case 5:
if (!Output(list))
printf("SeqList is empty!\n");
break;
case 6:
Reverse(&list);
break;
case 7:
Destroy(&list);
printf("the list is destroyed!\n");
break;
case 0:
break;
default:
printf("error input!\n");
}
} while (choice);
return 0;
}
4.线性表顺序存储优缺点
优点:
-
随机存取,数据定位和获取方便快速;
-
结构简单,易于实现。
-
存储密度高,为1,每个元素空间没有浪费的空间
缺点:
-
插入、删除效率低;
-
必须按事先估计的最大元素个数分配连续的存储空间,难以临时扩大。
二、线性表链接存储
(1) 单链表
-
带表头结点的单链表
-
循环单链表
(2)双链表
-
带表头结点的双链表
-
循环双链表
(3)静态链表
1.单链表存储表示法
注:
-
尾结点的 ^ 指针为null,地址值为0
-
逻辑上相邻的元素在物理上不一定相邻
-
不能出现“断链”现象
1.1单链表的结点结构
-
存储元素信息的数据域(数据)
-
存储直接后继结点首地址指针域()
2.单链表类型定义
typedef struct Node //结点数据结构
{
ElemType element; //结点的数据域
struct Node *link; //结点的指针域
}Node;
typedef struct //单链表数据结构
{
Node *first; //指向单链表头结点的指针
int n; //结点数量
}SingleList;
2.1初始化
Status Init(SingleList *L)//单链表为空的初始化
{
L->first=NULL;
L->n=0;
return OK;
}
2.2查找操作
- Find(L, i, x):查找元素ai(第i+1个元素)并通过x返回
Status Find(SingleList L, int i, ElemType *x)
{
Node *p;
int j;
if (i<0 || i>L.n-1) //对i 进行越界检查
return ERROR;
p=L.first;
for ( j=0; j<i; j++) //循环i次
p=p->link; //从首元结点开始查找ai
*x=p->element; //通过x 返回ai的值
return OK;
}
解释:
当指针p移动到a0~a1之间时:p=p->;link。如果想把p指向的元素表示出来:*x=p->element
(指针做形参变量如:ElemType *x,其值发生改变时用“ —> ”符号表示间接访问)
2.3插入操作
步骤:
(1)文字描述
-
生成数据域为x的新结点,q指针指向新结点;
-
从first(头指针)开始找到第 i+1 个结点,p指向该节点;
-
入链:将q插入p;
-
表长+1;
(2)图片解释:先1后2,顺序不能反。
Status Insert(SingleList *L, int i, ElemType x)
{
Node *p,*q;
int j;
if (i<-1 || i>L->n-1)
return ERROR;
p=L->first;
//从首元结点开始查找
for ( j=0; j<i; j++) p=p->link;
//生成新结点q
q=(Node*)malloc(sizeof(Node));
q->element=x;
if(i>-1){ //新结点插在p 之后
q->link=p->link;
p->link=q;
}
else{ //新结点插在首元结点之前
q->link=L->first;
L->first=q;
}
L->n++;//表长+1
return OK;
}
2.4删除操作
-
Delete(L, i):删除ai结点,0≤i ≤n-1
-
(a0,a1,…,ai…,an-1)
步骤:
(1)文字解释:
-
从first开始找第i+1个结点,p指向该结点,q指向p的前驱结点;
-
从单链表中删除p——>释放p之空间——>表长减1
(2)图解释:
-
先将q->link=p->link ;q指针 指向 p的指向位置
-
脱链:free(p);
Status Delete(SingleList *L,int i)
{
int j;
Node *p,*q;
if (!L->n)
return ERROR;
if (i<0 || i>L->n-1)
return ERROR;
q=L->first;
p=L->first;
for (j=0; j<i-1; j++)
q=q->link;
if (i==0) //删除的是首元结点
L->first=L->first->link; //p->link
else{ //删除的不是首元结点
p=q->link; //p 指向ai
q->link=p->link;
}
free(p); //释放p 结点的存储空间
L->n--;
return OK;
}
2.5输出操作
Status Output(SingleList *L)
{
Node *p;
if (!L->n ) //判断链表是否为空
return ERROR;
p=L->first;
while(p){
printf("%d ", p->element);
p=p->link;
}
printf ("\n");
return OK;
}
2.6销毁操作
void Destroy (SingleList *L)
{ Node *p;
while(L-> first )
{ p= L-> first->link;
free(L->first);
L-> first=p;
}
L->n = 0;
}
解释:
2.7主函数
int main()
{
int i, x;
SingleList list;
Init(&list); //对链表初始化
for (i = 0; i < 10; i++)
Insert(&list, i - 1, i); //链表中插入新元素
printf("the linklist is:");
Output(&list);
Delete(&list, 0);
printf("\nthe linklist is:");
Output(&list);
Find(list, 0, &x);
printf("\nthe value is:");
printf("%d ", x);
Destroy(&list);
return 0;
}
单链表存储代码演示
//SingleList.c:线性表的链接存储
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define ERROR 0
#define OK 1
#define Overflow 2 // Overflow 表示上溢
#define Underflow 3 // Underflow 表示下溢
#define NotPresent 4 // NotPresent 表示元素不存在
#define Duplicate 5 // Duplicate 表示有重复元素
typedef int Status;
typedef int ElemType;
typedef struct Node //结点数据结构
{
ElemType element; //结点的数据域
struct Node* link; //结点的指针域
}Node;
typedef struct //单链表数据结构
{
Node* first; //单链表头结点首地址
int n; //结点数量
}SingleList;
Status Init(SingleList* L)
{
L->first = NULL;
L->n = 0;
return OK;
}
Status Find(SingleList L, int i, ElemType* x)
{
Node* p;
int j;
if (i<0 || i>L.n - 1) //对i 进行越界检查
return ERROR;
p = L.first;
for (j = 0; j < i; j++)
p = p->link; //从头结点开始查找ai
*x = p->element; //通过x 返回ai的值
return OK;
}
Status Insert(SingleList* L, int i, ElemType x)
{
Node* p, * q;
int j;
if (i<-1 || i>L->n - 1)
return ERROR;
p = L->first;
//从头结点开始查找
for (j = 0; j < i; j++) p = p->link;
//生成新结点q
q = (Node*)malloc(sizeof(Node));
q->element = x;
if (i > -1) { //新结点插在p 之后
q->link = p->link;
p->link = q;
}
else { //新结点插在头结点之前
q->link = L->first;
L->first = q;
}
L->n++;
return OK;
}
Status Delete(SingleList* L, int i)
{
int j;
Node* p, * q;
if (!L->n)
return ERROR;
if (i<0 || i>L->n - 1)
return ERROR;
q = L->first;
p = L->first;
for (j = 0; j < i - 1; j++)//q停止在待删除结点的前一个位置
q = q->link;
if (i == 0) //删除的是头结点
L->first = L->first->link; //p->link
else { //删除的是非头结点
p = q->link; //p 指向第i+1个结点,即待删除的结点处
q->link = p->link; //脱链
}
free(p); //释放p 结点的存储空间
L->n--;
return OK;
}
Status Output(SingleList L)
{
Node* p;
if (!L.n) //判断链表是否为空
return ERROR;
p = L.first;
printf("Print all elements:\n");
while (p) {
printf("%d ", p->element);
p = p->link;
}
printf("\n");
return OK;
}
Status reverse(SingleList* L) //单链表的逆置
{
Node* p, * q;
if (!L->n) //判断链表是否为空
return ERROR;
p = L->first;
L->first = NULL;
while (p)
{
q = p->link; //待删除结点的下一个结点处
p->link = L->first; //指针调转方向
L->first = p; //头指针指向已经调整好的局部链表的第一个结点位置
p = q;
} //准备处理下一个结点
return OK;
}
void Destroy(SingleList* L)
{
Node* p;
while (L->first)
{
p = L->first->link;
free(L->first);
L->first = p;
}
L->n = 0;
}
/*
int main()
{
int i, x;
SingleList list;
Init(&list); //对链表初始化
for (i = 0; i < 10; i++)
Insert(&list, i - 1, i); //链表中插入新元素
printf("the linklist is:");
Output(&list);
Delete(&list, 0);
printf("\nthe linklist is:");
Output(&list);
Find(list, 0, &x);
printf("\nthe value is:");
printf("%d ", x);
Destroy(&list);
return 0;
}
*/
void menu()
{
printf("----1:初始化链表----\n");
printf("----2:插入元素----\n");
printf("----3:查找元素----\n");
printf("----4:删除元素----\n");
printf("----5:输出所有元素----\n");
printf("----6:链表逆置----\n");
printf("----7:撤销链表----\n");
printf("----0:退出程序----\n");
}
int main()
{
int choice, pos, elem;
SingleList list;
do
{
menu();
printf("Please input one choice(0--6)\n");
scanf("%d", &choice);
switch (choice)
{
case 1:
Init(&list); //对链表进行初始化
break;
case 2:
printf("Please input position and element:\n");
scanf("%d%d", &pos, &elem);
if (!Insert(&list, pos, elem))
printf("no space or error position!\n"); //线性表中插入新元素
break;
case 3:
printf("Please input position found:\n");
scanf("%d", &pos);
if (Find(list, pos, &elem))
printf("the element is:%d\n", elem);
else
printf("the position does not exist\n");
break;
case 4:
printf("Please input position found:\n");
scanf("%d", &pos);
if (Delete(&list, pos))
printf("one element is deleted!\n");
else
printf("no element is deleted!\n");
break;
case 5:
if (!Output(list))
printf("Singlelist is empty!\n");
break;
case 6:
reverse(&list);
break;
case 7:
Destroy(&list);
printf("the list is destroyed!\n");
break;
case 0:
break;
default:
printf("error input!\n");
}
} while (choice);
return 0;
}