数据结构与算法分析复习笔记(持续更新...欢迎指正)

数据结构与算法分析

文章目录


前言

对于每种数据结构与算法,都应该在理解其基础的情况下,将它的模板背过(找几道典型的题目,一开始不会写,就看懂答案后当做例题背过模板,再自己重新写出,重复以上操作,直到自己可以独立写出题目)——数学题也是这样学习的

如果跟着学校,就会被安排的明明白白,无论你学校的好坏,基本都没救了

CS学习之路只能靠自己!


一、数据结构概述

数据结构相关基本概念

1. 数据

信息的载体,是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识 别,并输入给计算机处理的符号集合。

比如我们现在常用的搜索引擎,一般会有网页、图片、视频等分类。 MP3 就是声音数据,图片当然是图像数据,视频就不用说了,而网页其实指的就是全部数 据的搜索,包括最重要的数字和字符等文字数据。

数据最终会以01二进制形式被计算机识别

2. 数据元素

是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理。 也被称为记录。

比如,在人类中,什么是数据元素呀? 当然是人了。
畜类呢? 牛、马、羊、鸡、猪、 狗等动物当然就是畜类的数据元素。

3. 数据项

一个数据元素可以自若干个数据项组成。

比如人这样的数据元素,可以有眼、耳、鼻、嘴、 手、脚这些数据项,也可以有 姓名、年龄、性别、出生地址、联系电话等数据项,具体有哪些数据项,要视你做的 系统来决定。

数据项是数据不可分割的最小单位。在数据结构这门课程中,我们把数据项定义 为最小单位,是有助于我们更好地解决问题。所以,记住了,数据项是数据的最小单 位。但真正讨论问题时,数据元素才是数据结构中建立数据模型的着眼点。就像我们 讨论一部电影时,是讨论这部电影角色这样的"数据元素",而不是针对这个角色的姓 名或者年龄这样的"数据项"去研究分析。

4. 数据对象

是性质相同的数据元素的集合,是数据的子集。

什么叫性质相同呢,是指数据元素具有相同数量和类型的数据项,比如,还是刚才的例子,人都有姓名、生日、性别等相同的数据项。

既然数据对象是数据的子集,在实际应用中,处理的数据元素通常具有相同性质 , 在不产生混淆的情况下,我们都将数据对象简称为数据

5. 数据结构

是相互之间存在一种或多种特定关系的数据元素的集舍。

结构,简单的理解就是关系,比如分子结构,就是说组成分子的原子之间的排列方式。严格点说, 结构是指各个组成部分相互搭配和排列的方式,在现实世界中,不同数据元素之间不是独立的,而是存在特定的关系,我们将这些关系称为结构。

按照视点的不同, 我们把数据结构分为逻辑结构和物理结构。

i. 逻辑结构:是指数据对象中数据元素之间的相互关系

• 集合结构
• 线性结构
• 树形结构
• 图形结构

ii. 物理结构 (很多书中也叫做存储结构,你只要在理解上把官们当一回事就可以了)
是指数据的逻辑结构在计算机中的存储形式。那么根据物理结构的定义,实际上就是如何把数据元素 存储到计算机的存储器中

• 顺序存储
• 链式存储

6. 程序结构

• 顺序结构
• 分支结构
• 循环结构

7. 数据类型

是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。

在 C 语言中,按照取值的不同,数据类型可以分为两

• 原子类型:是不可以再分解的基本类型,包括整型、实型、字符型等。(浮点数计算有误差!!!)
• 结构类型:自若干个类型组合而戚,是可以再分解的。例如,整型数组是由若干整型数据组成的。

在这里插入图片描述

8. 算法

• 算法具有五个基本特性: 输入、输出、 有穷性、确定性和可行性
• 算法设计的要求:正确性、可读性、健壮性、时间效率高和存储量低

在这里插入图片描述

基本结构简介

1.从集合到结构体

朴素集合论(最原始的集合论)中的定义,即集合是“确定的一堆东西”,集合里的“东西”则称为元素。现代的集合一般被定义为:由一个或多个确定的元素所构成的整体 。

C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。

结构体就是集合在C中的表示,几乎所有的数学概念都是建立在集合的基础上,所有的数据结构都是特殊的集合,所以在C中定义数据结构,必须使用结构体。

代码如下(示例):

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct 
{
   
    int a;
    char b;
    double c;
} s1;
 
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
   
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
 
//也可以用typedef创建新类型
typedef struct
{
   
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

//此结构体的声明包含了其他的结构体
struct COMPLEX
{
   
    char string[100];
    struct SIMPLE a;
};

// --------------------------------------------------------------

//此结构体的声明包含了指向自己类型的指针
struct NODE
{
   
    char string[100];
    struct NODE *next_node;
};

// --------------------------------------------------------------
//结构体变量的初始化

#include <stdio.h>
struct Books
{
   
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {
   "C 语言", "RUNOOB", "编程语言", 123456}; //从这里的 book = {...}也可以看出,结构体就是集合

 
int main()
{
   
    printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}


2.映射、函数、算法

函数也称映射(两个元素的集之间元素相互“对应”的关系)或变换,函数也是一种算法、操作、算符,映射在计算机中处处存在

对数据结构所有的操作可以归纳为创建、销毁、增删查改


3.线性结构、树形结构、图形结构

数列、级数、线性表都是序列

顺序表和单链表是重中之重,是后续所有数据结构的基础,必须熟练的掌握,但经过实践学习,很多时候思路是容易的,但是无法写出,都是由于C语言指针、结构体没有掌握,语言基础薄弱。所以在接下来继续学习的路上,我还会花费大量的时间打牢C的基础,完成基础的学习,待熟练之后,再继续推进


算法性能评价

1.算法的时间复杂度(无穷大之间的比阶—等价无穷大)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们给出这样的定义,输入规模 n 在没有限制的情况下,只要超过一个数值 N ,这个函数就总是大于另一个函数,我的称函数是渐近增长的。
在这里插入图片描述
在这里插入图片描述

常对幂指阶
0(1) < O(logn) < O(n) < O(nlogn) < 0(n2 ) < 0(n3 ) < 0(2") < O(n!) < O(n")

只保留阶数最高项!!!即数量级!!!

忽略常数项,忽略系数,系数化
在这里插入图片描述
例题:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
例题:
在这里插入图片描述
核心语句 m++执行的次数为:(注意双重求和)
在这里插入图片描述
在这里插入图片描述

2.算法的空间复杂度

声明变量带来的内存开销

例题:
在这里插入图片描述
例题:
在这里插入图片描述
例题:
在这里插入图片描述
例题:
在这里插入图片描述

函数递归调用带来的内存开销

例题:
在这里插入图片描述
在这里插入图片描述
例题:
在这里插入图片描述
在这里插入图片描述

二、线性结构

C语言基础

1.数组

C 传递数组给函数

如果想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。

方式 1
形式参数是一个指针(您可以在下一章中学习到有关指针的知识):

void myFunction(int *param)
{
   
.
.
.
}

方式 2
形式参数是一个已定义大小的数组:

void myFunction(int param[10])
{
   
.
.
.
}

方式 3
形式参数是一个未定义大小的数组:

void myFunction(int param[])
{
   
.
.
.
}

实例
现在,让我们来看下面这个函数,它把数组作为参数,同时还传递了另一个参数,根据所传的参数,会返回数组中各元素的平均值:

double getAverage(int arr[], int size)
{
   
  int    i;
  double avg;
  double sum;
 
  for (i = 0; i < size; ++i)
  {
   
    sum += arr[i];
  }
 
  avg = sum / size;
 
  return avg;
}

现在,让我们调用上面的函数,如下所示:

实例

#include <stdio.h>
 
/* 函数声明 */
double getAverage(int arr[], int size);
 
int main ()
{
   
   /* 带有 5 个元素的整型数组 */
   int balance[5] = {
   1000, 2, 3, 17, 50};
   double avg;
 
   /* 传递一个指向数组的指针作为参数 */
   avg = getAverage( balance, 5 ) ;
 
   /* 输出返回值 */
   printf( "平均值是: %f ", avg );
    
   return 0;
}
 
double getAverage(int arr[], int size)
{
   
  int    i;
  double avg;
  double sum=0;
 
  for (i = 0; i < size; ++i)
  {
   
    sum += arr[i];
  }
 
  avg = sum / size;
 
  return avg;
}

当上面的代码被编译和执行时,它会产生下列结果:

平均值是: 214.400000
您可以看到,就函数而言,数组的长度是无关紧要的,因为 C 不会对形式参数执行边界检查。

例题:使用数组( [蓝桥杯2015初赛]星系炸弹)

题目描述 在X星系的广袤空间中漂浮着许多X星人造“炸弹”,用来作为宇宙中的路标。 每个炸弹都可以设定多少天之后爆炸。
比如:阿尔法炸弹2015年1月1日放置,定时为15天,则它在2015年1月16日爆炸。
有一个贝塔炸弹,a年b月c日放置,定时为n天,请你计算它爆炸的准确日期。

输入
输入存在多组数据,每组数据输入一行,每一行输入四个正整数a,b,c,n
输入保证日期在1000-01-01到2020-01-01之间,且日期合法。 n不超过1000

输出
请填写该日期,格式为 yyyy-mm-dd 即4位年份2位月份2位日期。比如:2015-02-19
请严格按照格式书写。不能出现其它文字或符号。

样例输入 Copy
2015 1 1 15
2014 11 9 1000

样例输出 Copy
2015-01-16
2017-08-05

#include <iostream>
using namespace std;
#include <cstdio>

bool fff(int year) {
   
	if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0) {
   
	//分闰年(29天;4年1闰;闰年能被4整除但不能被100整除,或能被400整除,即四年一闰,百年不闰,四百年再闰。 例如,2000年是闰年,2100年则是平年)
		return true;
	}
	//平年(28天)
	return false;
}

int main() {
   
	int ms[13] = {
   0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	//数组第一位下标为0,只也为0,这样数组下标就可以与月份相同,便于书写
	int y, m, d, n;
	while (cin >> y >> m >> d >> n) {
   
		//将n拆成一天一天的加,满一个月(年)就进位
		for (int i = 0; i < n; i++) {
   
			//判断现在的年份是否为闰年,修改2月的天数
			if (fff(y)) {
   
				ms[2] = 29;
			} else {
   
				ms[2] = 28;
			}
			
			//满一个月,进一月
			if (++d > ms[m]) {
   
				m++;
				d = 1;
			}
			
			//满一年,进一个年
			if (m > 12) {
   
				y++;
				m = 1;
			}
		}
		printf("%d-%02d-%02d\n", y, m, d);//%02d表示占两个位置,如果位置不够,就用0来占。
	}
	return 0;
}


2.指针

#include <stdio.h>
 
int main ()
{
   
    int var_runoob = 10;
    int *p;              // 定义指针变量
    p = &var_runoob;	//p就是指针,而不能认为*p是指针;*p是p所指向的变量
 
   printf("var_runoob 变量的地址: %p\n", p);
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

var_runoob 变量的地址: 0x7ffeeaae08d8

递增一个指针
我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,数组可以看成一个指针常量。下面的程序递增变量指针,以便顺序访问数组中的每一个元素:

实例

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   
   int  var[] = {
   10, 100, 200};
   int  i, *ptr;
 
   /* 指针中的数组地址 */
   ptr = var; //将数组的首地址赋值给指针ptr
   
   for ( i = 0; i < MAX; i++)
   {
   
      printf("存储地址:var[%d] = %p\n", i, ptr );
      printf("存储值:var[%d] = %d\n", i, *ptr );
      
	/* 指向下一个位置 */
      ptr++;//ptr指向一片连续的内存(即数组),所以可以进行递增或递减运算
   }
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

存储地址:var[0] = e4a298cc
存储值:var[0] = 10
存储地址:var[1] = e4a298d0
存储值:var[1] = 100
存储地址:var[2] = e4a298d4
存储值:var[2] = 200

3.数组与指针

数组名即首地址,数组名就是一个指针常量
double balance[50];

balance 一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。因此,下面的程序片段把 p 赋值为 balance 的第一个元素的地址:

double *p;
double balance[10];
p = balance;

使用数组名作为常量指针是合法的,反之亦然。因此,*(balance + 4) 是一种访问 balance[4] 数据的合法方式。

指针类型的数组(指针组成的数组)

可能有一种情况,我们想要让数组存储指向 int 或 char 或其他数据类型的指针。下面是一个指向整数的指针数组的声明:

int *ptr[MAX];

在这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。下面的实例用到了三个整数,它们将存储在一个指针数组中,如下所示:

实例

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   
   int  var[] = {
   10, 100, 200};
   int i, *ptr[MAX];
 
   for ( i = 0; i < MAX; i++)
   {
   
      ptr[i] = &var[i]; /* 赋值为整数的地址 */
   }
   for ( i = 0; i < MAX; i++)
   {
   
      printf("Value of var[%d] = %d\n", i, *ptr[i] );
   }
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200

您也可以用一个指向字符的指针数组存储字符串列表,如下:

实例

#include <stdio.h>
 
const int MAX = 4;
 
int main ()
{
   
   const char *names[] = {
   
                   "Zara Ali",
                   "Hina Ali",
                   "Nuha Ali",
                   "Sara Ali",
   };
   int i = 0;
 
   for ( i = 0; i < MAX; i++)
   {
   
      printf("Value of names[%d] = %s\n", i, names[i] );
   }
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali

能接受指针作为参数的函数,也能接受数组作为参数

如下所示:

实例

#include <stdio.h>
 
/* 函数声明 */
double getAverage(int *arr, int size);
 
int main ()
{
   
   /* 带有 5 个元素的整型数组  */
   int balance[5] = {
   1000, 2, 3, 17, 50};
   double avg;
 
   /* 传递一个指向数组的指针作为参数 */
   avg = getAverage( balance, 5 ) ;
 
   /* 输出返回值  */
   printf("Average value is: %f\n", avg );
    
   return 0;
}

double getAverage(int *arr, int size)
{
   
  int    i, sum = 0;       
  double avg;          
 
  for (i = 0; i < size; ++i)
  {
   
    sum += arr[i];
  }
 
  avg = (double)sum / size;
 
  return avg;
}

当上面的代码被编译和执行时,它会产生下列结果:

Average value is: 214.40000

**注意:**不能直接传递数组int balance[],因为int balance[]是一个数组,只有数组名才是指针

4.结构体指针

指向结构的指针
您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:

struct Books *struct_pointer;

现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:

struct_pointer = &Book1;

为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符(自带解引用),如下所示:

struct_pointer->title;

在这里插入图片描述

顺序表

1. 顺续表的静态分配(普普通通的数组~)

#include <stdio.h>
#include <stdlib.h>
#define MaxSize 10
typedef struct {
   
	int data[MaxSize];//用静态的“数组”存放元素 
	int length;//顺序表的当前长度 
}SqList;

//基本操作:创建并初始化一个顺序表 
void InitList(SqList *L) {
   
	for(int i=0;i<MaxSize;i++){
   
		L->data[i]=0; //默认初始化顺序表元素都为0 
		L->length=0; //默认初始化顺序表长度为0 
	}
}

int main() {
   
	SqList *L; //声明一个顺序表指针L 
	InitList(L);//将指针L传入初始化函数 
	return 0;
}

2. 顺续表的动态分配(可变数组~在空间不够时,再开辟一块更大的空间)

#include <stdio.h>
#include <stdlib.h>
#define InitSize 10 //默认的最大长度 
typedef struct {
   
	int *data;//数组名data作为动态分配数组的指针
	int MaxSize;//顺序表的最大容量 
	int length;//顺序表的当前长度 
}SqList;

//基本操作:创建并初始化一个顺序表 
void InitList(SqList *L) {
   
	//用malloc()申请一片连续的内存空间,使data指向申请空间的首地址 
	L->data=(int*)malloc(InitSize*sizeof(int));
	L->MaxSize=InitSize;
	L->length=0;	 
}

//基本操作:动态增加数组的长度 (可变数组) 
void IncreaseSize(SqList *L,int len) {
   
	//让p指向data,即可变数组的首地址,目的是为了copy一份L,未来可以通过free(p)来回收原来L的内存 
	int *p=L->data;
	//新分配一块更大的内存空间 
	L->data=(int*)malloc
  • 27
    点赞
  • 133
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值