小肥柴慢慢手写数据结构(C篇)(1-1 线性表 ArrayList 原始版本)

小肥柴慢慢手写数据结构(C篇)(1-1 线性表 ArrayList 原始版本)

目录

1-1 为啥要有线性表

  1. 其实就是人们想用数组存一些元素,并提供一些操作,把这两个需求结合在一起做成一个工具,用起来乘手。
  2. 至于其他XXX特性啊,写完代码再返思效果更好。

1-2 自己出列一个单子(ADT),看看要做什么

  1. 先画一张图,然后中文写起来:
    ArrayList简图
       struct ArrayList {1)自定义类型的数组 data[]//Q12)当前元素个数  len或者size;//Q23)数组最大的容量capacity;//Q3
       }
   才动笔我们就发现可能存在三个问题:
   	Q1:这里的数组是否要用动态的?
   	A1:一开始自己写,代码量少,不用写的那么复杂,先来一款静态的,数组大小用一个宏MAX_SIZE定死,后面再改。

    Q2:当前元素个数len是十分重要的一个标记量,相比于当前最后一个元素的数组索引位置last,哪一个更好呢?
    A2:其实都差不多,len = last + 1,先用len,很多场合都希望得到当前元素个数。

    Q3:最大容量,要不要放在struct中作为标配?
    A3:考虑到后续还要修改成动态数组,加上吧,避免一直适用MAX_SIZE重构的时候到处改。

   其实真的写完程序了,这些问题都不是核心问题。
  1. 需要的功能
    先列一些基本的功能,做自己有把握很快能实现的,那些高大上的功能后面慢慢磨;细想数组元素的操作,无外乎“增、删、改、查”(CRUD),所以基本功能应该围绕这4个点来讨论。
    (1)生成一个ArrayList,最好是带*的,方便函数传参修改内容。
    (2)(“增”,add)向当前list中尝试添加一个元素,分为默认在末尾添加和指定位置添加。
    (3)(“查”,find/search)在当前list中尝试寻找给定元素item,找到了就返回该元素的位置(实际上是第一次出现的位置)。
    (4)(“删”,delete)在当前list尝试删除元素,分为尝试删除给定元素和尝试删除给定位置的元素。
    (5)(“改”,update)尝试修改当前list中指定位置的元素。
    (6)获取给定位置元素:利用数组的优势,根据索引(index)找到对应的数据。

以上就是咱们的ADT了,注释贴在头文件ArrayList.h中,对照着写代码。

1-3 边想边写

挨个实现,不是大神的就写一段、编一段,测一段。

  1. ArrayList.h 对照ADT注释编码
typedef int ElementType;
#ifndef _Array_List_h
#define _Array_List_h

#define MAX_SIZE (50)
#define OK (0)
#define ERROR (-1)
/*
struct ArrayList {
    (1)自定义类型的数组 data[];
    (2)当前元素个数  len或者size;
    (3)数组最大的容量capacity;
}
*/
struct ArrayList{
	ElementType data[MAX_SIZE];
	int len;
	int capacity;
};
typedef struct ArrayList *PtrArrayList;
typedef PtrArrayList List;
//(1)生成一个ArrayList,最好是带*的,方便函数传参修改内容。
List createList();
//(2)向当前list中尝试添加一个元素item,分为默认在末尾添加和指定位置pos添加
int addItem(List list, ElementType item, int pos);
int addItemTail(List list, ElementType item);
//(3)在当前list中尝试寻找给定元素item,找到了就返回该元素的位置(实际上是第一次出现的位置)
int findItem(const List list, ElementType item);
//(4)在当前list尝试删除元素,分为尝试删除给定元素item和尝试删除给定位置pos的元素
int removeItem(List list, ElementType item);
int removeByIndex(List list, int pos);
//(5)尝试修改当前list中指定位置pos的元素item
int setItem(List list, ElementType item, int pos);
//(6)尝试获取给定位置pos的元素
ElementType getItem(List list, int pos);
// 可能还有别的功能。。。
#endif

需要注意几个点,代码规范些:
(1)为了防止后头文件被重复引用,用了

#ifndef _Array_List_h
#define _Array_List_h
#endif

的常规处理方式
(2)typedef用于变量名称替换,翻车点(编译问和理解知识点),可以自行百度;通过

typedef struct ArrayList *PtrArrayList;
typedef PtrArrayList List;

操作可隐去部分指针操作,代码易读性友好一些,其实真的猛士会直接上 *PtrArrayList
(3)定义宏一定要记得用“()”护体,用“ / ”折行
(4)先定两个状态量方便后面用

#define OK (0)
#define ERROR (-1)
  1. ArrayList.c 对照.h头文件实现每个功能,先来createList()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ArrayList.h"
List createList(){
	List L = (PtrArrayList)malloc(sizeof(struct ArrayList)); //Q4
	if(L == NULL){
		printf("Out of memery, create List fail\n");
		return NULL;  //初始化list失败,返回空
	}
	
	memset(L->data, 0, MAX_SIZE); //Q5
	L->len = 0;
	L->capacity = MAX_SIZE;
}

此处两个讨论点:
Q4:malloc前面到底要不要加(xxx *)的强制转换?
A4:网上的答案基本上都能解决问题了,我仅仅想提醒大家这是一个对付编译器的编程习惯而已,实际跑代码过程中,我不加强制转换也是ok哒

Q5:memset到底要不要做
A5:我建议还是要做的,毕竟不希望出现“烫烫烫”,C/C++变量声明一定要初始化,这是好多企业的血泪教训。

还需注意struct中的“.”与“->”两种操作,还是有区别的,我们的实现简单,就没有做太多区别,主要还是看编译器。

此外,网上还有一种讨论:“使用完free(ptr)之后,是否需要将ptr = NULL?”,我个人认为要看具体的场景和语言,给出几篇参考
(1)【C语言】5. 指针free后为什么要刻意指向NULL、野指针(原因、解决)、悬垂指针
(2)为什么 C 语言的 free 函数最后不自己把指针指向 NULL? ⇒ 经典话语:“在有经验的程序员看来,你这个蠢操作把错误隐藏的更深、导致程序逻辑更复杂、更难排除错误了”
(3)内存管理:C语言中的Malloc/free是如何分配内存的

别着急往下编,先来个main()试一把createList(),及时排错,main.c:

int main(int argc, char *argv[]) {
	int i;
	List list = createList();
	printf("\nlist is empty? %d\n", isEmpty(list));
	return 0;
}

isEmpty()用于检测当前list是否完成初始化(指针不为NULL,data[]没有数据,len=0),真的猛士会直接debug检查。

ArrayList.h中添加定义

int isEmpty(List list);

ArrayList.c中添加实现

int isEmpty(List list){
	if(list == NULL)
		return ERROR;
	return (list->len==0);
}
  1. 完成add工作,在尾部插入相对简单,我们思考下指定位置添加元素,先画个图
    添加元素
    相当于所有元素向后挪一位,注意最后一个元素的角标处理和元素个数增加(len++);还要考虑(1)list为空(2)数组满了len==capacity(下个贴我们需要改进的点)(3)插入位置越界(pos<0 or pos>=capacity)三种无效/非法操作。
int addItem(List list, ElementType item, int pos){
 int i;
 if(list == NULL){
 	printf("\nlist is null\n");
 	return ERROR;
 } else if(list->len == list->capacity){
 	printf("\nlist is full!\n");
 	return ERROR;
 } else if(pos < 0 || pos >= list->capacity){
 	printf("\npos out of range!\n");
 	return ERROR;
 }
 
 for(i = list->len-1; i >= pos; i--)
 	list->data[i+1] = list->data[i];
 list->data[pos] = item;
 list->len++;
 
 return OK;
}

此处有两个讨论点:
P1:非法判断那么多条件,要不要写成一个函数呢?我认为是可行的,最好是看已经成型的开源库中他人的写法,这种讨论要适度。
P2:挪位置的循环操作中,到底是从最后一位last索引开始迭代,还是从pos索引开始迭代呢?其实选择从后面开始比较妥当,处理起来方便很多,不相信可以尝试从pos迭代。

  1. 默认添加元素方法addItemTail(),直接使用现有addItem()
int addItemTail(List list, ElementType item){
 return addItem(list, item, list->len);
}
  1. 寻找元素findItem()
int findItem(const List list, ElementType item){
 int i = 0;
 if(list == NULL)
 	return ERROR;
 
//	for(i = 0; i < list->len; i++){
//		if(list->data[i] == item)
//			return i;
//	}
//	return ERROR;
 while(i < list->len && list->data[i] != item)
 	i++;
 return i > (list->len - 1) ? ERROR : i;
}

(1)注意const的用法,就是不希望list内容被修改,在很多源码中都是常见操作
(2)此处讨论下到底用while好,还是用for好。其实都差不多,while看起来清爽些,大佬们也爱用,for对初学者有好些,条件比较次数其实都是一样的。

  1. 删除元素的实现 removeItem()与removeByIndex(),先画图
    删除元素
int removeItem(List list, ElementType item){
 int i;
 if(list == NULL){
 	printf("\nlist is null\n");
 	return ERROR;
 }
 
 int pos = findItem(list, item);
 if(pos != ERROR){
 	for(i = pos; i < list->len-1; i++)
 		list->data[i] = list->data[i+1];
 	list->data[list->len-1] = 0;
 	list->len--;
 	return pos;
 }
 
 return ERROR;
}
int removeByIndex(List list, int pos){
 int i;
 if(list == NULL){
 	printf("\nlist is null\n");
 	return ERROR;
 } else if(pos < 0 || pos >= list->len){
 	printf("\npos out of range!\n");
 	return ERROR;
 }
 
 for(i = pos; i < list->len-1; i++)
 	list->data[i] = list->data[i+1];
 list->data[list->len-1] = 0;
 list->len--;
 return OK;
}

如果不考虑输入异常,这两个功能是可以部分代码复用的;特别对于尝试删除元素的操作,其实是删除了list中第一个出现的该元素,我们的list是允许有重复元素存在的,这个操作的第一步就是通过find函数找到目标元素的索引位置。

  1. 接下来就是改和获取了
int setItem(List list, ElementType item, int pos){
 if(list == NULL){
 	printf("\nlist is null\n");
 	return ERROR;
 } else if(pos < 0 || pos >= list->len){
 	printf("\npos out of range!\n");
 	return ERROR;
 }
 
 list->data[pos] = item;
 return OK;
}

ElementType getItem(List list, int pos){
 if(list == NULL){
 	printf("\nlist is null\n");
 	return ERROR;
 } else if(pos < 0 || pos >= list->len){
 	printf("\npos out of range!\n");
 	return ERROR;
 }
 
 return list->data[pos];
}
  1. 别忘了打印功能
void printList(const List list){
 int i;
 if(list != NULL){
 	printf("\n[ ");
 	for(i = 0; i < list->len; i++)
 		printf("%d ", list->data[i]);
 	printf("]\n");
 }
}
  1. 最后统一贴一下各个文件的实现:
    ArrayList.h
typedef int ElementType;
#ifndef _Array_List_h
#define _Array_List_h

#define MAX_SIZE (50)
#define OK (0)
#define ERROR (-1)

struct ArrayList{
 ElementType data[MAX_SIZE];
 int len;
 int capacity;
};
typedef struct ArrayList *PtrArrayList;
typedef PtrArrayList List;

List createList();
int addItem(List list, ElementType item, int pos);
int addItemTail(List list, ElementType item);
int findItem(const List list, ElementType item);
int removeItem(List list, ElementType item);
int removeByIndex(List list, int pos);
int setItem(List list, ElementType item, int pos);
ElementType getItem(List list, int pos);
int isEmpty(List list);

void printList(const List list);
#endif

ArrayList.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ArrayList.h"

List createList(){
 List L = (PtrArrayList)malloc(sizeof(struct ArrayList));
 if(L == NULL){
 	printf("Out of memery, create List fail\n");
 	return NULL;
 }
 
 memset(L->data, 0, MAX_SIZE);
 L->len = 0;
 L->capacity = MAX_SIZE;
 return L;
}

int addItem(List list, ElementType item, int pos){
 int i;
 if(list == NULL){
 	printf("\nlist is null\n");
 	return ERROR;
 } else if(list->len == list->capacity){
 	printf("\nlist is full!\n");
 	return ERROR;
 } else if(pos < 0 || pos >= list->capacity){
 	printf("\npos out of range!\n");
 	return ERROR;
 }
 
 for(i = list->len-1; i >= pos; i--){
 	list->data[i+1] = list->data[i];
 }
 list->data[pos] = item;
 list->len++;
 
 return OK;
}

int addItemTail(List list, ElementType item){
 return addItem(list, item, list->len);
}

int findItem(const List list, ElementType item){
 int i = 0;
 if(list == NULL)
 	return ERROR;
 
//	for(i = 0; i < list->len; i++){
//		if(list->data[i] == item)
//			return i;
//	}
//	return ERROR;
 while(i < list->len && list->data[i] != item)
 	i++;
 return i > (list->len - 1) ? ERROR : i;
}

int removeItem(List list, ElementType item){
 int i;
 if(list == NULL){
 	printf("\nlist is null\n");
 	return ERROR;
 }
 
 int pos = findItem(list, item);
 if(pos != ERROR){
 	for(i = pos; i < list->len-1; i++)
 		list->data[i] = list->data[i+1];
 	list->data[list->len-1] = 0;
 	list->len--;
 	return pos;
 }
 
 return ERROR;
}
int removeByIndex(List list, int pos){
 int i;
 if(list == NULL){
 	printf("\nlist is null\n");
 	return ERROR;
 } else if(pos < 0 || pos >= list->len){
 	printf("\npos out of range!\n");
 	return ERROR;
 }
 
 for(i = pos; i < list->len-1; i++)
 	list->data[i] = list->data[i+1];
 list->data[list->len-1] = 0;
 list->len--;
 return OK;
}

int setItem(List list, ElementType item, int pos){
 if(list == NULL){
 	printf("\nlist is null\n");
 	return ERROR;
 } else if(pos < 0 || pos >= list->len){
 	printf("\npos out of range!\n");
 	return ERROR;
 }
 
 list->data[pos] = item;
 return OK;
}

ElementType getItem(List list, int pos){
 if(list == NULL){
 	printf("\nlist is null\n");
 	return ERROR;
 } else if(pos < 0 || pos >= list->len){
 	printf("\npos out of range!\n");
 	return ERROR;
 }
 
 return list->data[pos];
}

int isEmpty(List list){
 if(list == NULL)
 	return ERROR;
 return (list->len==0);
}

void printList(const List list){
 int i;
 if(list != NULL){
 	printf("\n[ ");
 	for(i = 0; i < list->len; i++)
 		printf("%d ", list->data[i]);
 	printf("]\n");
 }
}

调用和测试main.c,可以随便写,但注意需要测试边界场景

#include <stdio.h>
#include <stdlib.h>
#include "ArrayList.h"

/* run this program using the console pauser or add your own getch, system("pause") or input loop */

int main(int argc, char *argv[]) {
 int i;
 List list = createList();
 printf("\nlist is empty? %d\n", isEmpty(list));
 
 for(i = 0; i < 20; i++)
 	addItemTail(list, i);
 
 printf("\ninit data: len=%d\n", list->len);
 printList(list);
 
 printf("\n==============test add item===================\n");
 addItemTail(list, 100);
 printList(list);
 addItem(list, 200, 3);
 printList(list);
 addItem(list, 77, 0);
 printList(list);
 addItem(list, 99, MAX_SIZE);
 printList(list);
 addItem(list, 99, -1);
 printList(list);
 
 printf("\n==============test find item===================\n");
 printf("\nfind 77'pos=%d\n", findItem(list, 77));
 printf("\nfind 100'pos=%d\n", findItem(list, 100));
 printf("\nfind 20'pos=%d\n", findItem(list, 20));
 printf("\nfind 99'pos=%d\n", findItem(list, 99));
 
 printf("\n==============test remove item===================\n");
 printf("\nremove 10 =>pos=%d\n", removeItem(list, 10));
 printList(list);
 printf("\nremove 10 again =>pos=%d\n", removeItem(list, 10));
 printList(list);
 printf("\nremove 77 =>pos=%d\n", removeItem(list, 77));
 printList(list);
 printf("\nremove 100 =>pos=%d\n", removeItem(list, 100));
 printList(list);
 printf("\nremove pos=0, ret=%d\n", removeByIndex(list, 0));
 printList(list);	
 printf("\nremove pos=10, ret=%d\n", removeByIndex(list, 10));
 printList(list);	
 printf("\nremove pos=%d, ret=%d\n", list->len-1, removeByIndex(list, list->len-1));
 printList(list);	
 printf("\nremove pos=%d, ret=%d\n", list->len, removeByIndex(list, list->len));
 printList(list);
 printf("\nremove pos=%d, ret=%d\n", MAX_SIZE, removeByIndex(list, MAX_SIZE));
 printList(list);
 
 printf("\n==============test set item===================\n");
 printf("\nset -2 pos=%d ~~~ %d \n", 0, setItem(list, 0, -2));
 printList(list);	
 printf("\nset -10 pos=%d ~~~ %d \n", list->len-1, setItem(list, -10, list->len-1));
 printList(list);
 printf("\nset -100 pos=%d ~~~ %d \n", 7, setItem(list, -100, 7));
 printList(list);
 printf("\nset -50 pos=%d ~~~ %d \n", -1, setItem(list, -50, -1));
 printList(list);
 printf("\nset -60 pos=%d ~~~ %d \n", MAX_SIZE, setItem(list, -60, MAX_SIZE));
 printList(list);
 
 printf("\n==============test get item===================\n");
 printf("get pos=>%d = %d\n", 0, getItem(list, 0));
 printf("get pos=>%d = %d\n", -1, getItem(list, -1));
 printf("get pos=>%d = %d\n", list->len-1, getItem(list, list->len-1));
 printf("get pos=>%d = %d\n", MAX_SIZE, getItem(list, MAX_SIZE));
 
 printf("\nlist is empty? %d\n", isEmpty(list));
 
 return 0;
}

(闲得慌)可以尝试实现不同语言版本,顺便体会下它们的魅力,多看源码

1-4 反思一下

  1. 总感觉各种非法判断,把程序可读性降低了,有待改进。
  2. 写死大小的数组需变成动态的会更好用。
  3. 至于那些加入一个集合/数组/队列的初始化方式,有着丰富的变形,暂不考虑;类似于合并两个list的方法,sort排序等等问题,大家可以继续修改尝试,但最好还是等动态数组完成后再改更好。
  4. 明显可以看到,因为有数组的索引,ArrayList在获取元素方面(也就是查的能力)速度是有保障的(依靠索引),但缺点也很明显:插入和删除成本高;具体的定量讨论,等完成了单链表后再做。
  5. 不明白的语法细节,多看多记就行,但不要放过这些细节,面试会多少问道。
  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值