一、数据结构核心名词
- 数据
程序的操作对象,用于描述客观事物
特点:
1、可以输入到计算机
2、可以被计算机运行
- 数据对象
性质相同的数据元素的集合(类似于数组)
- 数据元素
组成数据的对象的基本单位
- 数据项
一个数据元素由若干个数据项组成
- 结构
数据元素之间不是独立的,存在特定的关系,这些关系即是结构
- 数据结构
指的是数据对象中的数据元素之间的关系
代码中的体现:
#include <stdio.h>
//声明一个结构体类型
struct Person{ //一种数据结构
char *name; //数据项--名字
char *title; //数据项--职称
int age; //数据项--年龄
};
int main(int argc, const char * argv[]) {
struct Person p1; //数据元素;
struct Person tArray[10]; //数据对象;
p1.age = 25; //数据项
p1.name = "小C"; //数据项
p1.title = "程序猿"; //数据项
return 0;
}
二、逻辑结构与物理结构区别
逻辑结构(数据和数据之间的逻辑关系
)
- 集合结构
所有的数据都属于同一集合
🌰:同学A、B、C、D都属于一班,他们之间没有先后顺序的,是平等的。
- 线性结构
数据和数据是 一对一 的
🌰:数组、线性表、字符串(由n个字符组成)、栈(先进后出)、队列
(先进先出)
- 树形结构
数据和数据是 一对多 的
- 图形结构
数据和数据是 多对多 的
物理结构(数据最终存储在内存当中
)
- 顺序存储结构
开辟一段连续的内存空间,然后依次存储进去
- 链式存储结构
不需要提前开辟一个连续的内存空间
顺序存储结构与链式存储结构的区别与优缺点
区别
:
1、链式存储结构的地址不一定是连续的,但顺序存储结构的内存地址一定是连续的;
2、链式存储结构适用于比较频繁地插入、删除、更新元素,而顺序存储结构适用于频繁的查询时使用
优缺点
:
1、空间上:顺序比链式节约空间
2、存储操作上:顺序支持随机存储,方便操作
3、插入和删除上:链式的要比顺序的方便(因为插入的话顺序也很方便,问题是顺序的插入要执行更大的空间复杂度,包括一个从表头索引以及索引后的元素后移,而链式是索引后,插入就完成了)
三、算法设计要求
算法
就是解决特定问题求解的描述,在计算机中表现为指令的有限序列,并且每个指令表示一个或多个操作
算法的特性
- 输入输出
- 有穷性
- 确定性
- 可行性
设计要求即衡量算法的要求
- 正确性
- 可读性
- 健壮性
- 时间效率高和储存量低
四、算法时间复杂度
大O表示法
1、用常数1取代运行时间中所有常数
🌰:3 -> O(1)
2、在修改运行次数函数中,只保留最高阶层
🌰:n3+2n2+5 -> O(n3)
3、如果在最高阶存在且不等于1,则去除这个项目相乘的常数
🌰:2n3 -> O(n3)
下面我们拿些🌰看看:
4.1、常数阶,时间复杂度为 O(1)
//1+1+1=3 O(1)
void testSum1(int n){
int sum = 0; //执行1次
sum = (1+n)*n/2; //执行1次
printf("testSum1:%d\n",sum);//执行1次
}
//1+1+1+1+1+1+1=7 O(1)
void testSum2(int n){
int sum = 0; //执行1次
sum = (1+n)*n/2; //执行1次
sum = (1+n)*n/2; //执行1次
sum = (1+n)*n/2; //执行1次
sum = (1+n)*n/2; //执行1次
sum = (1+n)*n/2; //执行1次
printf("testSum2:%d\n",sum);//执行1次
}
4.2、线性阶
//n+n=2n O(n)
void add2(int x,int n){
for (int i = 0; i < n; i++) { //执行n次
x = x+1; //执行n次
}
}
//1+n+1+n+1=2n+3 O(n)
void testSum3(int n){
int i,sum = 0; //执行1次
for (i = 1; i <= n; i++) { //执行n+1次
sum += i; //执行n次
}
printf("testSum3:%d\n",sum); //执行1次
}
4.3、对数阶
//log2 n+1 O(logn)
void testA(int n){
int count = 1; //执行1次
while (count < n) {
count = count * 2; //2的x次方等于n
//x=log2 n次
}
}
4.4、平方阶
//n+n^2+n^2=2n^2+n O(n^2)
void add3(int x,int n){
for (int i = 0; i< n; i++) { //执行n次
for (int j = 0; j < n ; j++) {//执行n*n次
x=x+1; //执行n*n次
}
}
}
//n+(n-1)+(n-2)+...+1 = n(n-1)/2 = n^2/2 + n/2
//等差数列公式:sn=n(a1+an)/2
//O(n^2)
void testSum4(int n){
int sum = 0;
for(int i = 0; i < n;i++)
for (int j = i; j < n; j++) {
sum += j;
}
}
//1+(n+1)+n(n+1)+n^2+n^2=2+3n^2+2n
//O(n^2)
void testSum5(int n){
int i,j,x=0,sum = 0; //执行1次
for (i = 1; i <= n; i++) { //执行n+1次
for (j = 1; j <= n; j++) { //执行n(n+1)
x++; //执行n*n次
sum = sum + x; //执行n*n次
}
}
}
4.5、立方阶
//1+n+n^2+n^3+n^3=2n^3+n^2+n+1
//O(n^3)
void testB(int n){
int sum = 1; //执行1次
for (int i = 0; i < n; i++) { //执行n次
for (int j = 0 ; j < n; j++) { //执行n*n次
for (int k = 0; k < n; k++) {//执行n*n*n次
sum = sum * 2; //执行n*n*n次
}
}
}
}
常见的时间复杂度
执行次数函数 | 阶 | 术语 |
---|---|---|
12 | O(1) | 常数阶 |
2n+3 | O(n) | 线性阶 |
3n2+2n+1 | O(n2) | 平方阶 |
5log2n+20 | O(logn) | 对数阶 |
2n+3nlog2 n+19 | O(nlogn) | nlogn阶 |
6n3+2n2+3n+4 | O(n3) | 立方阶 |
2n | O(2n) | 指数阶 |
O(1) < O(log n) < O(n) < O(nlog n) < O(n2) < O(n3) < O(2n)
五、算法空间复杂度计算
主要考虑算法执行时所需要额辅助空间
计算公式:S(n)=n(f(n))
其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数
问题:数组逆序,将一维数组a中的n个数逆序存放在愿数组中。
#include <stdio.h>
int main(int argc, const char * argv[]) {
// insert code here...
int n = 5;
int a[10] = {1,2,3,4,5,6,7,8,9,10};
//算法实现(1) 空间复杂度为:O(1)
//只使用到一个辅助
int temp;
for(int i = 0; i < n/2 ; i++){
temp = a[i];
a[i] = a[n-i-1];
a[n-i-1] = temp;
}
for(int i = 0;i < 10;i++)
{
printf("%d\n",a[i]);
}
//算法实现(2) 空间复杂度为:O(n)
int b[10] = {0};
for(int i = 0; i < n;i++){
b[i] = a[n-i-1];
}
for(int i = 0; i < n; i++){
a[i] = b[i];
}
for(int i = 0;i < 10;i++)
{
printf("%d\n",a[i]);
}
return 0;
}
衡量一个算法的时候是描述最坏的情况,如果最坏情况一致,那么再比较平均值
六、线性表----关于顺序存储的实现(增删改查)
6.1、顺序存储(逻辑相邻,物理存储地址相邻)
6.1.1、顺序表初始化
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
//定义结点
typedef struct{
ElemType data;
struct Node *next;
}sqlist;
//1.初始化
Status InitList(sqlist *L){
L->data = (sqlist)malloc(sizeof(ElemType)*MAXSIZE);
if(!L->data) return ERROR;
L->length=0;
return OK;
}
6.1.2、顺序表的插入
/*
初始条件:顺序线性表L已存在,1≤i≤ListLength(L);
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
*/
Status ListInsert(Sqlist *L,int i,ElemType e){
//i值不合法判断
if((i<1) || (i>L->length+1)) return ERROR;
//存储空间已满
if(L->length == MAXSIZE) return ERROR;
//插入数据不在表尾,则先移动出空余位置
if(i <= L->length){
for(int j = L->length-1; j>=i-1;j--){
//插入位置以及之后的位置后移动1位
L->data[j+1] = L->data[j];
}
}
//将新元素e 放入第i个位置上
L->data[i-1] = e;
//长度+1;
++L->length;
return OK;
}
6.1.3、顺序表的取值
Status GetElem(Sqlist L,int i, ElemType *e){
//判断i值是否合理, 若不合理,返回ERROR
if(i<1 || i > L.length) return ERROR;
//data[i-1]单元存储第i个数据元素.
*e = L.data[i-1];
return OK;
}
6.1.4、顺序表删除
/*
初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
操作结果: 删除L的第i个数据元素,L的长度减1
*/
Status ListDelete(Sqlist *L,int i){
//线性表为空
if(L->length == 0) return ERROR;
//i值不合法判断
if((i<1) || (i>L->length+1)) return ERROR;
for(int j = i; j < L->length;j++){
//被删除元素之后的元素向前移动
L->data[j-1] = L->data[j];
}
//表长度-1;
L->length --;
return OK;
}
6.1.5、清空顺序表
/* 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 */
Status ClearList(Sqlist *L)
{
L->length=0;
return OK;
}
6.1.6、判断顺序表清空
/* 初始条件:顺序线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
Status ListEmpty(Sqlist L)
{
if(L.length==0)
return TRUE;
else
return FALSE;
}
6.1.7、获取顺序表长度ListEmpty元素个数
int ListLength(Sqlist L)
{
return L.length;
}
6.1.8、顺序输出List
/* 初始条件:顺序线性表L已存在 */
/* 操作结果:依次对L的每个数据元素输出 */
Status TraverseList(Sqlist L)
{
int i;
for(i=0;i<L.length;i++)
printf("%d\n",L.data[i]);
printf("\n");
return OK;
}
6.1.9、顺序表查找元素并返回位置
/* 初始条件:顺序线性表L已存在 */
/* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int LocateElem(Sqlist L,ElemType e)
{
int i;
if (L.length==0) return 0;
for(i=0;i<L.length;i++)
{
if (L.data[i]==e)
break;
}
if(i>=L.length) return 0;
return i+1;
}
6.2、链式存储
6.2.1、初始化单链表线性表
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
//定义结点
typedef struct Node{
ElemType data;
struct Node *next;
}Node;
typedef struct Node * LinkList;
Status InitList(LinkList *L){
//产生头结点,并使用L指向此头结点
*L = (LinkList)malloc(sizeof(Node));
//存储空间分配失败
if(*L == NULL) return ERROR;
//将头结点的指针域置空
(*L)->next = NULL;
return OK;
}
6.2.2、单链表插入
/*
初始条件:顺序线性表L已存在,1≤i≤ListLength(L);
操作结果:在L中第i个位置之后插入新的数据元素e,L的长度加1;
*/
Status ListInsert(LinkList *L,int i,ElemType e){
int j;
LinkList p,s;
p = *L;
j = 1;
//寻找第i-1个结点
while (p && j<i) {
p = p->next;
++j;
}
//第i个元素不存在
if(!p || j>i) return ERROR;
//生成新结点s
s = (LinkList)malloc(sizeof(Node));
//将e赋值给s的数值域
s->data = e;
//将p的后继结点赋值给s的后继
s->next = p->next;
//将s赋值给p的后继
p->next = s;
return OK;
}
6.2.3、单链表取值
/*
初始条件: 顺序线性表L已存在,1≤i≤ListLength(L);
操作结果:用e返回L中第i个数据元素的值
*/
Status GetElem(LinkList L,int i,ElemType *e){
//j: 计数.
int j;
//声明结点p;
LinkList p;
//将结点p 指向链表L的第一个结点;
p = L->next;
//j计算=1;
j = 1;
//p不为空,且计算j不等于i,则循环继续
while (p && j<i) {
//p指向下一个结点
p = p->next;
++j;
}
//如果p为空或者j>i,则返回error
if(!p || j > i) return ERROR;
//e = p所指的结点的data
*e = p->data;
return OK;
}
6.2.4、单链表删除元素
/*
初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1
*/
Status ListDelete(LinkList *L,int i,ElemType *e){
int j;
LinkList p,q;
p = (*L)->next;
j = 1;
//查找第i-1个结点,p指向该结点
while (p->next && j<(i-1)) {
p = p->next;
++j;
}
//当i>n 或者 i<1 时,删除位置不合理
if (!(p->next) || (j>i-1)) return ERROR;
//q指向要删除的结点
q = p->next;
//将q的后继赋值给p的后继
p->next = q->next;
//将q结点中的数据给e
*e = q->data;
//让系统回收此结点,释放内存;
free(q);
return OK;
}
6.2.5、单链表前插入法
/* 随机产生n个元素值,建立带表头结点的单链线性表L(前插法)*/
void CreateListHead(LinkList *L, int n){
LinkList p;
//建立1个带头结点的单链表
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL;
//循环前插入随机数据
for(int i = 0; i < n;i++)
{
//生成新结点
p = (LinkList)malloc(sizeof(Node));
//i赋值给新结点的data
p->data = i;
//p->next = 头结点的L->next
p->next = (*L)->next;
//将结点P插入到头结点之后;
(*L)->next = p;
}
}
6.2.6、单链表后插入法
/* 随机产生n个元素值,建立带表头结点的单链线性表L(后插法)*/
void CreateListTail(LinkList *L, int n){
LinkList p,r;
//建立1个带头结点的单链表
*L = (LinkList)malloc(sizeof(Node));
//r指向尾部的结点
r = *L;
for (int i=0; i<n; i++) {
//生成新结点
p = (Node *)malloc(sizeof(Node));
p->data = i;
//将表尾终端结点的指针指向新结点
r->next = p;
//将当前的新结点定义为表尾终端结点
r = p;
}
//将尾指针的next = null
r->next = NULL;
}