郝斌-数据结构与算法笔记1-线性结构の顺序存储[数组]

2020/4/9 19:01:25 人评论 次浏览 分类:学习教程

郝斌-数据结构与算法笔记1-线性结构の顺序存储[数组]

    • 1. 预备知识
      • 1.1 概念
      • 1.2 结构体
      • 1.3 指针
      • 指针
    • 2. 顺序存储代码与讲解
      • 2.1 全部程序
      • 2.2 课堂重点讲解

我的数据结构学习之路:
数据结构从大二就想学习,当时看的是浙江大学陈越姥姥的网课和书,看不懂听不懂难受,遂放弃。大四深圳实习,技术老大说你不学数据结构怎么可以,把他上大学时的书借我让我学,由于实习期间实在学不动,遂放弃。研一上学期,又想学,看的是严蔚敏奶奶的书-伪代码!又放弃。要学呀,不然校招了,故研二上学期寒假之前,看了一遍《大话数据结构》,自己写写代码有一个大体的认识,起码发现心理不畏惧这门课了。研二疫情期间,玩了两个月,该学学了吧。B站上偶遇郝斌老师的数据结构课程,适合我这种学渣。所以就把上课做的笔记,代码解释和以前不理解的地方和我自己的理解以博客的方式整理出来。

1. 预备知识

1.1 概念

  • 数据结构定义:研究数据存储问题;数据结构=个体的存储+个体关系的存储

  • 算法定义:解题的方法和步骤,研究数据操作问题

  • 算法特性

    1. 时间复杂度:大概程序要执行的次数,而非执行的时间

    2. 空间复杂度:算法执行过程中大概所占用的最大内存

    3. 可读性

    4. 健壮性

  • 数据结构和算法的关系

程序(Program)=数据结构(Data Structure)+算法(Algorithm)

1.2 结构体

  • 概念讲解1:结构体变量作为函数调用问题
#include <stdio.h>

struct Student
{
	int sid;
	char name[200];
	int age;
};	//208个字节,在传值时注意内存消耗问题

void InitStruct(struct Student * pst)
{	
	// pst是指针变量,用(->访问结构体变量)。(*pst) 是指针所指向的变量(用 . )
	(*pst).sid = 99;
	strcpy(pst->name, "ILOVEYOU");
	pst->age = 22;
}

void PrintStruct(struct Student st)		
{
	// 传入结构体变量即可,但是这种方法消耗内存,不推荐
    //因为传入变量名之后需要在内存中复制此结构体
	printf("%d  %s  %d\n",st.sid,st.name,st.age);
}
void PrintStruct2(struct Student * st)		
{
	// 传入结构体变量地址,节省内存
	printf("%d  %s  %d\n",st->sid,st->name,st->age);
}
int main(void)
{
	struct Student st;
	InitStruct(&st);
	PrintStruct(st);
	PrintStruct2(st);    
	return 0;
}

输出结果为:
99  ILOVEYOU  22
99  ILOVEYOU  22

概念讲解2:动态内存分配问题
malloc()函数返回分配内存的首地址,故需要赋值给一个指针。

struct Student * p = (struct Student *)malloc(sizeof(struct Student));
free(p);

概念讲解3:跨函数使用内存的问题:
在函数CreateStudent(void)中分配了一段内存用来存储结构体变量,并给结构体成员分配了值。而在另一个函数PrintStruct(struct Student *pst)中传入该结构体的地址,这个函数并没有重新分配一段内存来复制 p,而是直接访问该地址。

#include <stdio.h>
#include <malloc.h>

struct Student
{
	int sid;
	int age;
};	
// 指针作为函数返回值
//	此函数名称为CreateStudent(),返回值类型为Struct Student类的地址
struct Student * CreateStudent(void)
{	
	struct Student * p = (struct Student *)malloc(sizeof(struct Student));
	p->age=99;
	p->sid=40;
	return p;	
	//指针就是地址,地址就是指针,这里返回的就是一个地址
}

void PrintStruct(struct Student *pst)		
{
	printf("%d  %d\n",pst->sid,pst->age);
}

int main(void)
{
	struct Student * ps;
	
	ps = CreateStudent();
	PrintStruct(ps);

	return 0;
}

输出结果为:40,99

1.3 指针

指针

一句话:指针就是地址,地址就是指针

  • 概念讲解1:
    指针变量是存放内存单元地址的变量,他也是一个变量,只不过这个变量规定只能存储地址(见下图例子)。
    在这里插入图片描述
  • 概念讲解2:
    指针型变量作为函数形参时要注意区分其意义。此处表示函数function()定义了一个形参,这个形参变量的名称为p, 类型为 int * (即int 类型的地址)。
void function(int * p);

在主函数中调用方式如下,function()中传入的是变量地址:

void main()
{
	int i;
	function(&i);	//传入的是 i 的地址,对应函数定义中的(int *)
}
  • 概念讲解3:
    指针存放的是数组的首地址
#include <stdio.h>

void Show_Array(int * p, int len)
{
	int i;
	p[0] = -1;			//p[0]==*p  p[2]=*(p+2) == a[2] == *(a+2)
						//也就是p[i] == a[i]
	for(i=0;i<len;i++)
		printf("%d\n",p[i]);
}

int main(void)
{
	int a[5] = {1,2,3,4,5};

	Show_Array(a,5);	//a等价&a[0],&a[0]本身就是int * 类型

	printf("%d\n",a[2]);
}
  • 概念讲解4:
    所有的指针变量只占4个字节,用第一个字节的地址表示整个变量的地址;
void main()
{
	int *p;
    func(&p);	//形参传入的是指针的地址,用来修改指针指向。
}
void func(int **q)
{
    *q = (int *)malloc(sizeof(int)*4);
    // *q == p
}

2. 顺序存储代码与讲解

2.1 全部程序

以下是线性结构-顺序存储方式[数组],操作有初始化、追加、插入、删除、数组满,空检测、排序、反转和显示。建议看过代码之后自己多试着完整地写几遍。我看完《大话数据结构》之后就感觉这个挺简单的。

#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>

#define MAXSIZE 10
#define True 1
#define False 0

struct Arr
{
	int * pBase;	//存储的是第一个元素的地址
	int len;		//数组的长度
	int cnt;		//当前数组元素的有效元素数量
};

void init_arr(struct Arr * pArr, int length);
int append_arr(struct Arr * pArr, int value);	//追加
int insert_arr(struct Arr * pArr, int pos, int value);
int delete_arr(struct Arr * pArr, int pos, int * pvalue);
int get(struct Arr * pArr, int pos, int * pvalue);
int is_empty(struct Arr * pArr);
int is_full(struct Arr * pArr);
void sort_arr(struct Arr * pArr);
void show_arr(struct Arr * pArr);
void inversion_arr(struct Arr * pArr);

int main(void)
{
	struct Arr arr;
	int val;

	init_arr(&arr, MAXSIZE);
	append_arr(&arr, 3);
	append_arr(&arr, 1);
	append_arr(&arr, -1);
	append_arr(&arr, 66);
	append_arr(&arr, 7);
	append_arr(&arr, 5);
	append_arr(&arr, 8);
	append_arr(&arr, 11);
	append_arr(&arr, 6);
	insert_arr(&arr, 5, 100);
	show_arr(&arr);	//这里注意形参
	delete_arr(&arr, 6, &val);
	printf("删除的值为:%d\n", val);
	show_arr(&arr);	
	inversion_arr(&arr);
	show_arr(&arr);	
	sort_arr(&arr);
	show_arr(&arr);	
	get(&arr, 6, &val);
	printf("查找的值为:%d\n", val);
	show_arr(&arr);	
	return 0;
}

void init_arr(struct Arr * pArr, int length)
{	
	//传过来的是arr的地址值,那么此时*pArr就是arr     (*pArr).len = 100;

	pArr->pBase = (int *)malloc(sizeof(int)*length);
	if(NULL == pArr->pBase)
	{
		printf("动态内存分配失败!\n");
		exit(-1);	//终止整个程序
	}
	else
	{
		pArr->cnt = 0;
		pArr->len= length;
	}
	return;
}

int is_empty(struct Arr * pArr)
{
	if(0 == pArr->cnt)
		return True;
	else
		return False;
}

int is_full(struct Arr * pArr)
{
	if(pArr->len == pArr->cnt)
		return True;
	else
		return False;
}

void show_arr(struct Arr * pArr)
{
	int i=0;

	if( is_empty(pArr) )
	{
		printf("当前数组为空!\n");
	}
	else
	{
		for(i=0; i<pArr->cnt; i++)
			printf("%d ",pArr->pBase[i]);
		printf("\n");
	}
}

int append_arr(struct Arr * pArr, int value)
{
	if( is_full(pArr) )
	{
		printf("追加失败,数组已满!\n");
		return False;
	}

	pArr->pBase[pArr->cnt] = value;
	pArr->cnt++;
	return True;
}

int insert_arr(struct Arr * pArr, int pos, int value)
{	// pos 指的是第几个位置,不代表数组下表
	// 这里要注意位置和下表之间的关系;
	int i;	//用来指示数组下标
	if( is_full(&pArr) )
	{
		printf("插入失败,数组已满!\n");
		return False;
	}

	if(pos<1 || pos > pArr->cnt+1)		//pos>pArr->cnt+1:在pos<=pArr->cnt时,正常插入。在pos=pArr->cnt+1时,是插入到当前数组的末尾	
	{
		printf("插入失败,位置溢出!\n");
		return False;
	}	
	for(i=pArr->cnt-1;i>=pos-1;i--)
	{
		pArr->pBase[i+1]=pArr->pBase[i];
	}
	pArr->pBase[pos-1]=value;
	pArr->cnt++;
	return True;
}

int delete_arr(struct Arr * pArr, int pos, int * pvalue)
{
	//pos 指的是位置,i 指的是下表
	int i;
	if( is_empty(pArr) )
	{
		printf("插入失败,数组为空!\n");
		return False;
	}
	if(pos<1 || pos > pArr->cnt)		
	{
		printf("删除失败,位置溢出!\n");
		return False;
	}
	*pvalue = pArr->pBase[pos-1];
	for(i=pos; i<pArr->cnt;i++)		//	for(i=pos; i<=pArr->cnt-1;i++)
	{
		pArr->pBase[i-1]=pArr->pBase[i];
	}
	pArr->cnt--;
}

void inversion_arr(struct Arr * pArr)
{
	int i = 0;
	int j = pArr->cnt-1;//下标
	int t;

	while(i<j)
	{
		t = pArr->pBase[i];
		pArr->pBase[i] = pArr->pBase[j];
		pArr->pBase[j] = t;
		i++;j--;
	}
}

void sort_arr(struct Arr * pArr)
{
	int i,j,t;
	for(i=0; i<pArr->cnt; i++)
		for(j=i+1; j<pArr->cnt; j++)
		{
			if(pArr->pBase[i]>pArr->pBase[j])	//冒泡排序:大数挪到后面,升序排列
			{
				t = pArr->pBase[i];
				pArr->pBase[i]=pArr->pBase[j];
				pArr->pBase[j]=t;
			}
		}
}

int get(struct Arr * pArr, int pos, int *pvalue)
{
	if(is_empty(pArr))
	{
		printf("查找失败,数组为空!\n");
	}
	if(pos<1 || pos > pArr->cnt)		
	{
		printf("删除失败,位置溢出!\n");
		return False;
	}
	*pvalue = pArr->pBase[pos-1];
}

2.2 课堂重点讲解

这里主要就is_empty()函数和is_full()函数调用形参时进行说明。因为两函数情况相同,这里以is_full()为例。

// 函数定义:
int is_full(struct Arr * pArr)
{
	if(pArr->len == pArr->cnt)
		return True;
	else
		return False;
}

// 函数调用:
int append_arr(struct Arr * pArr, int value)
{
	if( is_full(pArr) )
	{
		printf("追加失败,数组已满!\n");
		return False;
	}

	pArr->pBase[pArr->cnt] = value;
	pArr->cnt++;
	return True;
}

is_full(struct Arr * pArr) 的形参名称为 pArr,类型为struct Arr * (是结构体变量类型的地址)。

append_arr(struct Arr * pArr, int value)的形参名称为pArr,类型为struct Arr * (是结构体变量类型的地址)。

if( is_full(pArr) )这句话在调用is_full(struct Arr * pArr) 时用if( is_full(pArr) ),而不是if( is_full(&pArr) )的原因是:

append_arr(&arr, 3)传入的形参是结构体变量arr的地址,而is_full(struct Arr * pArr) 的形参也为结构体变量的地址,pArr就代表了&arr(即arr的地址), 所以可以直接把pArr传给is_dull();

相关资讯

    暂无相关的资讯...

共有访客发表了评论 网友评论

验证码: 看不清楚?
    -->