文章目录
一、可变数组
1.实现
定义一种数据结构Array,里头放了一个整形指针array和用来表明当前这个数组的长度的size。
a.头文件
def.h
#ifndef _ARRAY_H_
#define _ARRAY_H_
#define BLOCK_SIZE 10
typedef struct{
int *array;
int size;
}Array;
Array array_create( int init_size);
//创建可变数组
void array_free(Array *a);
//因为你是申请的指针,最后要释放指针
int array_size(const Array *a);
//返回可变数组当前的大小
int* array_at(Array *a,int index);
//返回指针可以赋值数组中的元素
int array_get(Array* a,int index);
//返回array[index]
void array_inflate(Array *a,int more_size);
//让可变数组成长
void array_write(Array*a,int index,int value);
//修改array[index]的值
#endif
都以指针为参数的原因是方便直接修改对应值,不用返回参数。
b.初始化可变数组
初始化可变数组就是要创建一个这样的结构体并给定最初数组的最大元素个数,为了能在main函数里用我们选择返回这种结构体类型的函数。
Array array_create( int init_size)
{
Array a;
a.size=init_size;
a.array=(int*)malloc(sizeof(int)*a.size);
return a;
}
c.删除可变数组
删除可变数组要做的事情就是释放array所指的内存空间,并且把size变回0,把array赋NULL。
void array_free(Array *a)
{
free(a->array);
a->size=0;
a->array=NULL;
}
d.可变数组的当前最大元素个数
int array_size(const Array *a)
{
return a->size;
}
为什么不在main中直接return a->size而是要定义一个函数array_size?
- 这是一种封装的思想。
- 在面向对象编程中
- 封装(encapsulation)是将对象运行所需的资源封装在程序对象中
- 对象是“公布其接口”。
- 其他附加到这些接口上的对象不需要关心对象实现的方法即可使用这个对象。
- 这个概念就是“不要告诉我你是怎么做的,只要做就可以了。”
- 把内部实现细节保护起来了
- 对象可以看作是一个自我包含的原子。对象接口包括了公共的方法和初始化数据。
e.访问可变数组的值
两种思路,一种是直接用一个返回int*的函数array_at,这样如果要修改i位置的值为number只需要 *array_at(&a,i)=number。
另一种方法是我用array_get函数来查看数组对应元素的值,用array_write函数来修改数组对应元素的值。
这两种思路都要注意,当我们输入的i比当前可变数组最大元素个数大时,我们要让数组成长,利用后面会介绍的array_inflate函数就可以。
int* array_at(Array *a,int index)
{
if(a->size<=index)
{
array_inflate(a,(index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
//index/BLOCK_SIZE得到index在第几个BLOCK然后加1 再乘BLOCK_SIZE 再减去a->size
//得到一次加BLOCK_SIZE
}//这样我们的array at就能自动增长了
return &(a->array[index]);
}
//另一种实现方法
int array_get(Array* a,int index)
{
return a->array[index];
}
void array_write(Array*a,int index,int value)
{
if(index>=a->size)
{
array_inflate(a,(index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
}
a->array[index]=value;
}
f.可变数组的成长
总体思路就是利用malloc函数申请一块内存,大小为原数组大小与more_size的和乘以sizeof(int)个字节,然后利用一个for循环或利用memcpy函数把array数组中的内容拷贝到p数组中,然后释放原内存空间free(array),然后把让array等于p,size=size+more_size。
void array_inflate(Array *a,int more_size)
{
int* p=(int*)malloc(sizeof(int)*(a->size + more_size));
int i;
// for(i=0;i<a->size;i++)
// {
// p[i]=a->array[i];
// }
memcpy((void*)p,(void*)a->array,sizeof(int)*a->size);
free(a->array);
a->array=p;
a->size+=more_size;
}
g.总实验
def.h
#ifndef _ARRAY_H_
#define _ARRAY_H_
#define BLOCK_SIZE 10
typedef struct{
int *array;
int size;
}Array;
Array array_create( int init_size);//创建可变数组
void array_free(Array *a);//因为你是申请的指针,最后要释放指针
int array_size(const Array *a);//返回可变数组当前的大小
int* array_at(Array *a,int index);//返回指针可以赋值数组中的元素
int array_get(Array* a,int index);//返回array[index]
void array_inflate(Array *a,int more_size);//让可变数组成长
void array_write(Array*a,int index,int value);//修改array[index]的值
#endif
array.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "def.h"
// typedef struct{
// int *array;
// int size;
// }Array;
Array array_create( int init_size)
{
Array a;
a.size=init_size;
a.array=(int*)malloc(sizeof(int)*a.size);
return a;
}
void array_free(Array *a)
{
free(a->array);
a->size=0;
a->array=NULL;
}
int array_size(const Array *a)
{
return a->size;
}
int* array_at(Array *a,int index)
{
if(a->size<=index)
{
array_inflate(a,(index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
//index/BLOCK_SIZE得到在第几个BLOCK然后加1 再乘BLOCK_SIZE 再减去a->size
//得到一次加BLOCK_SIZE
}//这样我们的array at就能自动增长了
return &(a->array[index]);
}
//另一种实现方法
int array_get(Array* a,int index)
{
return a->array[index];
}
void array_write(Array*a,int index,int value)
{
if(index>=a->size)
{
array_inflate(a,(index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size);
}
a->array[index]=value;
}
void array_inflate(Array *a,int more_size)
{
int* p=(int*)malloc(sizeof(int)*(a->size + more_size));
int i;
// for(i=0;i<a->size;i++)
// {
// p[i]=a->array[i];
// }
memcpy((void*)p,(void*)a->array,sizeof(int)*a->size);
free(a->array);
a->array=p;
a->size+=more_size;
}
int main()
{
Array a=array_create(10);
printf("number of array is %d\n",array_size(&a));
*array_at(&a,0)=10;//这样就可以可以把值写到那个数组里头去
printf("array[0]=%d\n",*array_at(&a,0));
//如果觉得返回一个指针然后再用*给它赋值难受 我们可以提供另一种思路
array_write(&a,11,521);
printf("array[11]=%d\n",array_get(&a,11));
int count=1;
int number;
while(number!=-1)
{
scanf("%d",&number);
if(number!=-1)
{
*array_at(&a,count++)=number;
}
//scanf("%d",array_at(&a,count++));
//不停录入数据 达到上限就自动增长
}
for(int j=0;j<count;j++)
{
printf("array[%d]=%d\n",j,*array_at(&a,j));
}
array_free(&a);
return 0;
}
h. 动态顺序表的汇总
//Seqlist.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int SLDatatype;
//这样写想让这个数据结构存储什么类型的数据就在这里改就行了。
typedef struct Seqlist {
SLDatatype* data;
int size;//表示有效数据的个数
int capcity;//空间容量
}SL;
void SeqlistInit(SL* ps);
void SeqlistPushfront(SL* ps, SLDatatype x);
void SeqlistPushback(SL* ps, SLDatatype x);
void SeqlistPopfront(SL* ps, SLDatatype* x);
void SeqlistPopback(SL* ps, SLDatatype* x);
void Seqlistdestroy(SL* ps);
void checkcapcity(SL* ps);//检查扩容
void SeqPrint(SL* ps);
int Seqlistfind(SL* ps, SLDatatype x);//找到了返回下标,找不到返回-1
void SeqlistInsert(SL* ps, int pos, SLDatatype x);//在pos下标处插入元素
void SeqlistErase(SL* ps, int pos, SLDatatype* x);//删除pos下标的元素
//Seqlist.c
#include "Seqlist.h"
void SeqlistInit(SL* ps)
{
//ps->data = (SLDatatype*)malloc(N * sizeof(SLDatatype));
//ps->capcity = N;
//ps->size = 0;
ps->data = NULL;
ps->size = ps->capcity = 0;
}
void SeqlistPushfront(SL* ps, SLDatatype x)
{
//checkcapcity(ps);
//int end = ps->size - 1;
//while (end>=0)
//{
// ps->data[end + 1] = ps->data[end];
// end--;
//}
//ps->data[0] = x;
//ps->size++;
SeqlistInsert(ps, 0, x);
}
//1.整个顺序表没有空间
//2.空间需要扩容
//3.空间足够直接插入
void SeqlistPushback(SL* ps, SLDatatype x)
{
//checkcapcity(ps);
//ps->data[ps->size] = x;
//ps->size++;
SeqlistInsert(ps, ps->size, x);
}
void SeqlistPopfront(SL* ps, SLDatatype* x)
{
//assert(ps->size > 0);
//*x = ps->data[0];
//for (int i = 0; i < ps->size - 1; i++)
//{
// ps->data[i] = ps->data[i + 1];
//}
//ps->size--;
SeqlistErase(ps, 0, x);
}
void SeqlistPopback(SL* ps, SLDatatype* x)
{
//assert(ps->size>0);
//ps->size--;
//if (ps->size != 0)
//{
// *x = ps->data[ps->size-1];
// ps->size--;
//}
//else
//{
// printf("顺序表没有元素了已经\n");
//}
SeqlistErase(ps, ps->size - 1, x);
}
void Seqlistdestroy(SL* ps)
{
free(ps->data);
ps->capcity = ps->size = 0;
}
void SeqPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ",ps->data[i]);
}
}
void checkcapcity(SL* ps)
{
if (ps->size == ps->capcity)
{
int newcapcity = ps->capcity == 0 ? 4 : ps->capcity * 2;
SLDatatype* tmp = (SLDatatype*)realloc(ps->data, newcapcity * sizeof(SLDatatype));
if (tmp == NULL)
{
printf("realloc fault\n");
exit(-1);//终止程序
}
ps->data = tmp;
ps->capcity = newcapcity;
}
}
int Seqlistfind(SL* ps, SLDatatype x)
{
int ret = -1;
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (x == ps->data[i])
{
ret = i;
break;
}
}
return ret;
}
void SeqlistInsert(SL* ps, int pos, SLDatatype x)
{
assert(pos <= ps->size && pos >= 0);
checkcapcity(ps);
int i;
for (i = ps->size-1; i>=pos; i--)
{
ps->data[i + 1] = ps->data[i];
}
ps->data[pos] = x;
ps->size++;
}
void SeqlistErase(SL* ps, int pos, SLDatatype* x)
{
assert(ps->size > 0 );
if (pos<0 || pos>ps->size)
{
printf("位置输入错误\n");
return;
}
*x = ps->data[pos];
for (int i = pos; i < ps->size-1; i++)
{
ps->data[i] = ps->data[i + 1];
}
ps->size--;
}
#include "Seqlist.h"
void TestSeqlist1()
{
SL sl;
SeqlistInit(&sl);
SeqlistPushback(&sl, 1);
SeqlistPushback(&sl, 2);
SeqlistPushback(&sl, 3);
SeqlistPushback(&sl, 4);
SeqlistPushback(&sl, 5);
SeqPrint(&sl);
printf("\n");
SLDatatype arr[5];
for (int i = 0; i < 5; i++)
{
SeqlistPopback(&sl, &arr[i]);
printf("%d ", arr[i]);
}
}
void TestSeqlist2()
{
SL sl;
SeqlistInit(&sl);
SeqlistPushback(&sl, 1);
SeqlistPushback(&sl, 2);
SeqlistPushback(&sl, 3);
SeqlistPushback(&sl, 4);
SeqlistPushback(&sl, 5);
SeqlistPushfront(&sl, 0);
SeqlistPushfront(&sl, -1);
SeqlistPushfront(&sl, -2);
SeqlistPushfront(&sl, -3);
SeqlistPushfront(&sl, -4);
SeqlistPushfront(&sl, -5);
SeqPrint(&sl);
SLDatatype arr[10];
printf("\n");
for (int i = 0; i < 10; i++)
{
SeqlistPopfront(&sl, &arr[i]);
printf("%d ", arr[i]);
}
printf("\n");
SeqPrint(&sl);
}
void TestSeqlist3()
{
SL sl;
SeqlistInit(&sl);
SeqlistPushback(&sl, 1);
SeqlistPushback(&sl, 2);
SeqlistPushback(&sl, 3);
SeqlistPushback(&sl, 4);
SeqlistPushback(&sl, 5);
int a = Seqlistfind(&sl, 2);
int b = Seqlistfind(&sl, 5);
int c = Seqlistfind(&sl, 6);
printf("%d %d %d\n", a, b, c);
SeqlistInsert(&sl, 3, 10);
SeqlistInsert(&sl, 5, 11);
SeqlistInsert(&sl, 2, 12);
SeqPrint(&sl);
int arr[3];
SeqlistErase(&sl, 3,&arr[0]);
SeqlistErase(&sl, 5, &arr[1]);
SeqlistErase(&sl, 2, &arr[2]);
printf("\n");
for (int i = 0; i < 3; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
SeqPrint(&sl);
}
int main()
{
TestSeqlist1();
return 0;
}
2.可变数组的缺陷:
-
首先,拷贝要花时间,如果数据很大拷贝要花很多时间;
-
其次,每次都要重新申请一个更大的内存空间,总有一次会出现这种情况:剩余内存是前面的你free过的n-BS大小的空间, 你现在的a->array的内存空间是n,后面只有2BS的空间,你下一次要身申请n+BS的空间但是你却你申请不到空间了,尽管空间剩余内存是够的,但是你却申请不到了,因为这个可变数组内存是连续的。
-
所以说可变数组可以用,但是不够高效。
3.memcpy函数
作用:直接复制数组
void *memcpy(void *str1, const void *str2, size_t n);
参数
-
str1 – 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
-
str2 – 指向要复制的数据源,类型强制转换为 void* 指针。
-
n – 要被复制的字节数。
返回值
该函数返回一个指向目标存储区 str1 的指针。
这就引出了链表的动机,如果我们可以这样,数组不够了以后,我就再申请一块和原本一样长的地方,并且把前一个地方和下一个地方通过某种手段连起来,告诉计算机你访问完这个去访问下一个连起来的地方,这样是一种不连续的内存,这就解决了拷贝的问题,也解决了每次都要重新申请一个更大的内存空间的问题,可以充分利用这个内存的每块角角落落。
头文件
string.h
二、链表
链表是这样一种数据结构,它由很多节点连接而成,它的每个节点中装有此节点的数据data和指向下一个节点的指针next,并且头结点有一个头指针head指着他,尾结点指向NULL。
1.创建链表函数
这里的想法是分为两类讨论:
如果该链表还一个节点都没有,那么此时头结点指向NULL,那么我们让头指针等于这个p,也就是头指针指向这个头结点;
如果这个链表已经有一定的长度了,那么此时头结点一定指向非空,那么我们利用尾结点指向NULL这一特点利用一个while循环让last指向尾结点,然后last->next=p连接起来。
typedef struct _node{
int data;
struct _node* next;
}Node;
void add(Node* head,int number)
{
//创建一个节点
Node*p=(Node*)malloc(sizeof(Node));
p->data=number;
p->next=NULL;
Node* last= head;
//分两种情况,它是头结点或者它不是头结点
if(last!=NULL)//这说明head不等于NULL 这也就说明我们新建的节点前面有链表
{
while(last->next!=NULL)
//最后一个节点的next是指向空的 我们要让last走到最后一个节点
{
last=last->next;
}
last->next=p;//连接
}
else//说明head等于NULL 这个节点就是头结点
{
head=p;
}
}
问题:
不难发现创建链表函数其实本质上是要不断更改head,最初head=NULL变成head->next=NULL再变成head->next->next=NULL以此类推;因此有这样一个问题,你传进函数的head是本地变量,修改了也不会对main函数中的head有影响,就会一直是空链表。
第一种解决方法:
算法书上会这样做,我们直接把head定义成全局变量就好了。
Node* head;
但是这样是有缺陷的。首先这是一次性的,我们的add函数只能对这个全局变量起作用,如果程序中有多个链表呢;其次返回全局变量是不安全的 **“由于函数如果返回静态本地变量或全局变量的地址,那么此函数为不可从入的,会导致线程不安全(丰田汽车的案子),**因此不要使用全局变量在函数之间传递参数,尽量避免使用全局变量。”
第二种解决方式:
既然要改head,返回head就是了。
typedef struct _node{
int data;
struct _node* next;
}Node;
Node* add(Node* head,int number)
{
//创建一个节点
Node*p=(Node*)malloc(sizeof(Node));
p->data=number;
p->next=NULL;
Node* last= head;
//分两种情况,它是头结点或者它不是头结点
if(last!=NULL)//这说明head不等于NULL 这也就说明我们新建的节点前面有链表
{
while(last->next!=NULL)
//最后一个节点的next是指向空的 我们要让last走到最后一个节点
{
last=last->next;
}
last->next=p;//连接
}
else//说明head等于NULL 这个节点就是头结点
{
head=p;
}
return head;
}
这样的add函数是线程安全的,并且可以针对不同的LinkedList。
但是这样仍有点小毛病,因为返回head,所以我们要保证使用此函数的程序员这样写:
head=add(head,number);
可没人强迫他这样写,万一他忘了呢?
第三种解决方案:
既然要改head,我们干脆传head的指针进去算了。
typedef struct _node{
int data;
struct _node* next;
}Node;
void add(Node** phead,int number)
{
//创建一个节点
Node*p=(Node*)malloc(sizeof(Node));
p->data=number;
p->next=NULL;
Node* last= *phead;
//分两种情况,它是头结点或者它不是头结点
if(last!=NULL)//这说明head不等于NULL 这也就说明我们新建的节点前面有链表
{
while(last->next!=NULL)
//最后一个节点的next是指向空的 我们要让last走到最后一个节点
{
last=last->next;
}
last->next=p;//连接
}
else//说明head等于NULL 这个节点就是头结点
{
*phead=p;
}
}
第四种解决方案:
定义一个数据类型List,里头可以放整个链表,比如我可以同时放head和tail(tail是指向尾节点的指针),然后我们向函数传指向List的指针,因为List中有head和tail指针,这样也相当于有了指向它们的指针就不会出现修改不了head的问题了。
typedef struct _node{
int data;
struct _node* next;
}Node;
typedef struct _list{
Node* head;
Node* tail;
}List;
void add(List* pList,int number)
{
//创建一个节点
Node*p=(Node*)malloc(sizeof(Node));
p->data=number;
p->next=NULL;
//分两种情况,它是头结点或者它不是头结点
if(pList->head!=NULL)//这说明head不等于NULL 这也就说明我们新建的节点前面有链表
{
(pList->tail)->next=p;//连接
pList->tail=p;//让tail指到尾结点
}//这样就不必一遍一遍的把last从头节点弄到尾结点去了。
else//说明head等于NULL 这个节点就是头结点
{
pList->head=pList->tail=p;
}
}
int main()
{
int number;
List list;
list.head=list.tail=NULL;
do{
scanf("%d",&number);
if(number!=-1)
{
add(&list,number);
}
}while(number!=-1);
return (0);
}
这个方案的优势在于通过自己定义一个数据类型List给我们改进List带来了无限的可能。
2.链表的遍历
利用一个for循环p指向空时跳出。
void print(List* pList)
{
Node* p;
for(p=pList->head;p;p=p->next)//p还存在 相当于p!=NULL
{
printf("%d\t",p->data);
}
printf("\n");
}
3.链表的删除
删除节点这件事我们考虑这样来做:p指向待删除节点,q指向p之前的节点,然后让q->next=p->next把q指的位置和p下一个位置连上,然后free§。
这里有个问题,如果p是头结点呢,这时q不存在,不能用上面的方法,这种情况要让head->next=p->next,然后free§。
所以我们可以先控制好q=NULL,如果头结点就满足条件那么此时q=NULL,其他情况q都会指向p前一个节点,满足q!=NULL.
int found(List* pList,int number)
{
Node*p;
int isFound=0;
for(p=pList->head;p;p=p->next)
{
if(p->data==number)
{
isFound=1;
printf("找到了\n");
break;
}
}
if(!isFound)
{
printf("没找到\n");
}
return isFound;
}
void delete(List* pList,int number)
{
if(found(pList,number)==1)
{
Node* p;
Node* q;
for(q=NULL,p=pList->head;p; q=p,p=p->next)
//这样在找到的时候。q指的就是p前一个位置
{
if(p->data==number)
{
if(q==NULL)
//如果直接就是头结点呢,此时q就是null,你要改的就是head //这种问题找边界条件,就盯着所有->前面的东西,看他们什么时候等于NULL
{
pList->head=p->next;
}
else
{
q->next=p->next;
}
free(p);
break;
}
}
}
else{
printf("你要删除元素不存在");
}
}
4.链表的清除
因为我们所有的链表节点都是malloc出来的,我们总要找个时间free他们。
void clear(List* pList)
{
Node* p;
Node* q;
for(p=pList->head;p;)
//想法是让q等于p的下一个节点 然后free p 然后把q的值给p
{
q=p->next;
free(p);
p=q;
}
}
5.总实验
node.h
#ifndef _NODE_H_
#define _NODE_H_
typedef struct _node{
int data;
struct _node* next;
}Node;
typedef struct _list{
Node* head;
Node* tail;
}List;
#endif
main.c
#include <stdio.h>
#include "node.h"
#include <stdlib.h>
void add(List* pList,int number);
void print(List*pList);
int found(List* pList,int number);
void delete(List* pList,int number);
void clear(List* pList);
int main()
{
int number;
// Node* head=NULL;//一开始一个节点没有,头结点指空
List list;
list.head=list.tail=NULL;
do{
scanf("%d",&number);
if(number!=-1)
{
add(&list,number);
}
}while(number!=-1);
print(&list);
int num;
scanf("%d",&num);
found(&list,num);
delete(&list,num);
print(&list);
clear(&list);
return (0);
}
void add(List* pList,int number)
{
//创建一个节点
Node*p=(Node*)malloc(sizeof(Node));
p->data=number;
p->next=NULL;
//分两种情况,它是头结点或者它不是头结点
if(pList->head!=NULL)//这说明head不等于NULL 这也就说明我们新建的节点前面有链表
{
(pList->tail)->next=p;//连接
pList->tail=p;//让tail指到尾结点
}
else//说明head等于NULL 这个节点就是头结点
{
pList->head=pList->tail=p;
}
}
void print(List* pList)
{
Node* p;
for(p=pList->head;p;p=p->next)//p还存在 相当于p!=NULL
{
printf("%d\t",p->data);
}
printf("\n");
}
int found(List* pList,int number)
{
Node*p;
int isFound=0;
for(p=pList->head;p;p=p->next)
{
if(p->data==number)
{
isFound=1;
printf("找到了\n");
break;
}
}
if(!isFound)
{
printf("没找到\n");
}
return isFound;
}
void delete(List* pList,int number)
{
if(found(pList,number)==1)
{
Node* p;
Node* q;
for(q=NULL,p=pList->head;p; q=p,p=p->next)//这样在找到的时候。q指的就是p前一个位置
{
if(p->data==number)
{
if(q==NULL)//如果直接就是头结点呢,此时q就是null,你要改的就是head
//这种问题找边界条件,就盯着所有->前面的东西,看他们什么时候等于NULL
{
pList->head=p->next;
}
else
{
q->next=p->next;
}
free(p);
break;
}
}
}
else{
printf("你要删除元素不存在");
}
}
void clear(List* pList)
{
Node* p;
Node* q;
for(p=pList->head;p;)
//想法是让q等于p的下一个节点 然后free p 然后把q的值给p
{
q=p->next;
free(p);
p=q;
}
}