动态数组(C语言)
// C语言版 数据结构-动态顺序表的实现
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
const int MAXLISTSIZE = 80; //预设的存储空间最大容量
#define OK 1 //符号常量定义
#define FALSE 0
#define ERROR -1
#define OVERFLOW -2
typedef int Status; //特殊数据类型定义
typedef int ElemType;
#define LISTINCREMENT 3 //动态顺序表的增长步长
#define LIST_INIT_SIZE 10 //动态顺序表的长度
//顺序表的动态数组实现的存储结构定义
typedef struct
{
ElemType *elem; //动态数组的首地址
int length; //动态数组的实际存储数据的长度
int listsize; //动态数组的大小(以sizeof(ElemType)为单位)
}SqList;
//功能:构造一个空的顺序表L,初始化成果返回OK,算法2.3
Status Initlist_Sq(SqList &L)
{
L.elem = (ElemType *)malloc(LIST_INIT_SIZE * sizeof(ElemType));
if(!L.elem)
{
printf("存储空间分配失败!\n");
exit(OVERFLOW);
}
L.length = 0; //空表实际数据个数为0
L.listsize = LIST_INIT_SIZE; //数组的大小为分配空间的大小
return OK;
}
//功能:在顺序表L中第i个元素之前插入一个元素e, 算法2.4
Status ListInsert_Sq(SqList &L, int i, ElemType e)
{
int k;
ElemType *newbase, *q; //指针方式操作数组元素
if((i < 1) || (i > L.length + 1)) //插入位置不合法
{
printf("错误提示:插入位置不合法!\n");
return ERROR;
}
if(L.length >= L.listsize) //存储空间已满,增加空间
{
newbase = (ElemType *)realloc(L.elem, (LIST_INIT_SIZE+LISTINCREMENT)*sizeof(ElemType));
if(!newbase)
{
printf("错误提示:存储空间已满,新增空间失败!\n");
exit(OVERFLOW);
}
L.elem = newbase; //修改数组首地址为新分配空间的首地址
L.listsize += LISTINCREMENT; //修改数组长度为新分配的空间大小
}
q = &(L.elem[i-1]); //使用指针的方式操作数组元素,q为第i个元素的地址
for(k = L.length - 1; k >= i - 1; k--) //为插入数据e移动位置
L.elem[k+1] = L.elem[k];
L.elem[i - 1] = e; //C语言,数组中第i个数据的下标为i-1
L.length++; //插入一个数据后,顺序表的长度增1
return OK;
}
//功能:删除顺序表L中第i个元素,并删除元素的值存放到e中
Status ListDelete_Sq(SqList &L, int i, ElemType &e)
{
ElemType *p, *q; //指针方式操作数组元素
if((i < 1)||(i > L.length)) //位置i不合法
{
printf("错误提示:删除数据的位置不合法!\n");
return ERROR;
}
p = &(L.elem[i - 1]); //使用指针的方式操作数组元素,p为被删除元素的地址位置
e = *p; //保存被删除元素
q = L.elem + L.length - 1; //表尾元素的位置
for(++p; p <= q; ++p) //被删除元素之后的元素左移1位
*(p-1) = *p;
L.length--; //删除完成后,顺序表的长度减1
return OK;
}
//功能:在顺序表L中查找元素e是否存在,存在则返回元素所在的位置,否则返回0
int LocateElem_Sq(SqList L, ElemType e)
{
int i;
ElemType *p;
i = 0; //第一个元素的位序
p = L.elem; //第一个元素的存储地址
while((i <= L.length) && (L.elem[i] != e)) //顺序扫描,直到找到值为e的元素,或扫描到表尾而没找到
i++;
if(i < L.length)
return i + 1; //找到,返回其序号,此处已经和人心中的数据位置一致了,后面就不要加1了
else
return 0; //没找到,返回空序号
return OK;
}
//功能:检测顺序表是否为空,如果为空,则返回1,否则返回0
Status Listempty_Sq(SqList L)
{
if(L.length == 0) //判断顺序表的实际长度是否为0
return 1;
else
return 0;
return OK;
}
//功能:将顺序表中的所有数据输出
void OutputList_Sq(SqList L)
{
int i;
if(L.length == 0) //判断顺序表是否为空
printf("错误提示:顺序表长 = 0,是空表,没有数据!");
else
{
printf("顺序表长 = %d。其数据元素分别为: \n", L.length);
for(i = 0; i < L.length; i++)
printf("第 %d 个数据: %d \n", i + 1, L.elem[i]);
}
}
//功能:向空动态顺序表输入n个数据
void InputList_Sq(SqList &L, int n)
{
int i;
for(i = 0; i < n; i++)
{
printf("第 %d 个元素: ", i + 1);
scanf("%d", &L.elem[i]);
}
L.length = n;
}
//功能:将顺序表中的数据清除,并将表长置为0
void Clearlist_Sq(SqList &L)
{
int i;
for(i = 0; i < L.length; i++) //将表中的数据全部置为0
L.elem[i++] = 0;
L.length = 0; //清除数据后,表的实际数据个数为0
}
//功能:菜单1的操作处理,实现:输入数据到动态顺序表中
void SubMenu1(SqList &L)
{
int n;
char sel;
if(L.length > 0)
{
printf("动态顺序表非空,初始化后原有数据将丢失,是否要真的重新初始化(Y/N)?\n");
getchar(); //去掉输入的多余换行符
scanf("%c", &sel);
if(tolower(sel) == 'y')
{
printf("动态顺序表数据个数:");
scanf("%d", &n);
InputList_Sq(L, n); //调用数据输入功能,向动态顺序表中输入数据
printf("动态顺序表数据初始化成功!\n");
}
}
else
{
printf("动态顺序表数据个数:");
scanf("%d", &n);
InputList_Sq(L, n); //调用数据输入功能,向动态顺序表中输入数据
printf("动态顺序表数据初始化成功!\n");
}
}
//功能:菜单3的操作处理,实现:动态顺序表数据插入
void SubMenu3(SqList &L)
{
int i;
ElemType e;
printf("插入数据位置:");
scanf("%d", &i);
printf("插入数据:");
scanf("%d", &e);
if(ListInsert_Sq(L, i, e) == OK) //调用插入数据功能去插入数据
printf("成功将数据 %d 插入到动态顺序表中!\n", e);
else
printf("插入数据到动态顺序表中失败!\n");
}
//功能:菜单4的操作处理,实现:动态顺序表数据删除
void SubMenu4(SqList &L)
{
int i;
ElemType e;
printf("删除数据位置:");
scanf("%d", &i);
if(ListDelete_Sq(L, i, e) == OK) //调用动态顺序表数据删除功能去删除数据
printf("成功将动态顺序表中的数据 %d 删除!\n", e);
else
printf("删除失败!\n");
}
//功能:菜单5的操作处理,实现:动态顺序表数据查询
void SubMenu5(SqList &L)
{
//LocateElem_Sq
int i;
ElemType e;
printf("要查询数据:");
scanf("%d", &e);
i = LocateElem_Sq(L, e); //调用查询数据功能去查询数据
if((i < L.length) && (i > 0))
printf("在动态顺序表的第 %d 个位置找到数据 %d。\n", i, e);
else
printf("对不起,在动态顺序表中没有找到数据 %d。\n", e);
}
//功能:显示处理菜单
void ShowMenu()
{
printf("\n*******************顺序表的动态数组实现*******************\n");
printf("\n\t\t1. 动态顺序表数据初始化\n");
printf("\n\t\t2. 动态顺序表数据输出\n");
printf("\n\t\t3. 动态顺序表数据插入\n");
printf("\n\t\t4. 动态顺序表数据删除\n");
printf("\n\t\t5. 动态顺序表数据查询\n");
printf("\n\t\t6. 动态顺序表数据清空\n");
printf("\n\t\t0. 退出程序\n");
printf("\n*******************顺序表的动态数组实现*******************\n");
}
void main()
{
SqList L;
int choice;
printf("正在初始化动态顺序表,请稍等......\n");
if(Initlist_Sq(L)) //动态顺序表由于事先没分配存储空间来存储数据,因此要先进行初始化
printf("\n动态顺序表初始化成功!\n");
else
{
printf("错误提示:动态顺序表初始化失败,请重新启动程序!\n");
getchar(); system("CLS");
exit(0);
}
while(1)
{
system("CLS");
ShowMenu();
printf("功能选择: ");
scanf("%d",&choice);
switch(choice)
{
case 1:
{
SubMenu1(L); //调用菜单1的功能
fflush(stdin);
system("pause");
break;
}
case 2:
{
OutputList_Sq(L); //调用动态顺序表数据输出功能
fflush(stdin);
system("pause");
break;
}
case 3:
{
SubMenu3(L); //调用菜单3的功能
fflush(stdin);
system("pause");
break;
}
case 4:
{
SubMenu4(L); //调用菜单4的功能
fflush(stdin);
system("pause");
break;
}
case 5:
{
SubMenu5(L); //调用菜单5的功能
fflush(stdin);
system("pause");
break;
}
case 6:
{
Clearlist_Sq(L); //清除动态顺序表中的所有数据
fflush(stdin);
system("pause");
break;
}
case 0:
{
fflush(stdin);
printf("谢谢使用!\n");
exit(0);
}
default:
{
printf("功能选择错误,只能选择0-5!\n");
fflush(stdin);
system("pause");
}
}
}
}
动态数组(Java语言)
数组的作用:把数据码成一排进行存放
数组初始化的两种方法
- 指定容量
int[] arr = new int[20];
- 指定初始值
int[] scores = new int[]{100, 99, 66};
二次封装我们自己的数组
/**
* 二次封装我们自己的数组
* Created by binzhang on 2019/3/15.
*/
public class Array {
private int[] data;
private int size;
// 构造函数,传入数组的容量capacity构造Array
public Array(int capacity){
data = new int[capacity];
size = 0;
}
// 无参数的构造函数,默认数组的容量capacity=10
public Array(){
this(10);
}
// 获取数组中的元素个数
public int getSize(){
return size;
}
// 获取数组的容量
public int getCapacity(){
return data.length;
}
// 返回数组是否为空
public boolean isEmpty(){
return size == 0;
}
}
添加方法
size是数组的第一个空元素的定位符。
// 向所有元素后添加一个新元素
public void addList(int e){
if(size == data.length)
throw new IllegalArgumentException("AddLast failed . Array is full.");
data[size] = e;
size ++;
}
当遇到下面场景即向指定位置添加元素时要怎么办呢?
首先将100后移一个位置,再依次将99,88后移一个位置,然后将77插入到索引为1的位置。
// 在第index个位置插入一个新元素e
public void add(int index, int e){
if(size == data.length)
throw new IllegalArgumentException("AddLast failed . Array is full.");
if(index < 0 || index > size)
throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");
for (int i = size -1; i>= index; i --)
data[i + 1] = data[i];
data[index] = e;
size ++;
}
这里其实可以在addLast里面复用我们的add方法了。
// 向所有元素后添加一个新元素
public void addList(int e){
add(size, e);
}
在所有元素前添加一个新元素
// 在所有元素前添加一个新元素
public void addFirst(int e){
add(0,e);
}
重写toString方法
//重新toString方法输出数组信息
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append(String.format("Array: size = %d , capacity = %d\n", size, data.length));
res.append('[');
for (int i = 0 ; i < size ; i ++){
res.append(data[i]);
if(i != size-1)
res.append(", ");
}
res.append(']');
return res.toString();
}
验证toString方法和在指定位置插入元素方法输出:
public class Main {
public static void main(String[] args) {
Array arr = new Array(20);
for (int i = 0 ; i < 10 ; i ++){
arr.addLast(i);
}
System.out.println(arr);
}
}
输出:
Array: size = 10 , capacity = 20
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array: size = 11 , capacity = 20
[0, 100, 1, 2, 3, 4, 5, 6, 7, 8, 9]
数组中查询元素和修改元素
// 获取index索引位置的元素
public int get(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("Add failed. Index is illegal.");
return data[index];
}
public E getLast(){
return get(size - 1);
}
public E getFirst(){
return get(0);
}
// 修改index索引位置的元素为e
public void set(int index, int e){
if(index < 0 || index >= size)
throw new IllegalArgumentException("Add failed. Index is illegal.");
data[index] = e;
}
数组中的包含、搜索和删除元素
// 查找数组中是否有元素e
public boolean contains(int e){
for (int i = 0 ; i < size ; i ++){
if (data[i] == e)
return true;
}
return false;
}
// 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
public int find(int e){
for (int i = 0 ; i < size ; i ++){
if(data[i] == e)
return i;
}
return -1;
}
如下场景,想要删除索引为1的值为77的元素
将77后的元素都向左移一位即可,最后将size–
// 从数组中删除index位置的元素,返回删除的元素
public int remove(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("Add failed. Index is illegal.");
int res = data[index];
for (int i = index + 1 ; i < size ; i ++)
data[i - 1] = data[i];
size --;
data[size] = null;
return res;
}
对应的可以写出相应的扩展方法
// 从数组中删除第一个元素,返回删除的元素
public int removeFirst(){
return remove(0);
}
// 从数组中删除最后一个元素,返回删除的元素
public int removeLast(){
return remove(size - 1);
}
// 从数组中删除元素e
public void removeElement(int e){
int index = find(e);
if(index != -1)
remove(index);
}
泛型数组
泛型数组
- 让我们的数据结构可以放置“任何”数据类型
- 不可以是基本数据类型,只能是类对象
- boolean byte char short int long float double
- 每个基本数据类型都有对应的包装类(可以使用)
- Boolean Byte Char Short Int Long Float Double
定义泛型数组:
/**
* 定义泛型数组
* Created by binzhang on 2019/3/15.
*/
public class Array<E> {
private E[] data;
private int size;
// 构造函数,传入数组的容量capacity构造Array
public Array(int capacity){
data = (E[])new Object[capacity];
size = 0;
}
...
}
声明泛型数组要传入要定义的类型
Array<Integer> arr = new Array<>(20);
泛型数组使用示例:
/**
* Created by binzhang on 2019/3/16.
*/
public class Student {
private String name;
private int score;
public Student(String studentName, int studentScore){
this.name = studentName;
this.score = studentScore;
}
@Override
public String toString() {
return String.format("Student(name: %s, score: %d)", name, score);
}
public static void main(String[] args) {
Array<Student> arr = new Array<>();
arr.addLast(new Student("Alice", 100));
arr.addLast(new Student("Bob", 99));
arr.addLast(new Student("chili", 88));
System.out.println(arr);
}
}
输出:
ArrayOld: size = 3 , capacity = 10
[Student(name: Alice, score: 100), Student(name: Bob, score: 99), Student(name: chili, score: 88)]
动态数组
动态数组可以简单理解为动态扩容,如下图所示当我们的data数组原容量只有4,且里面已经有四个元素,无法再次添加,我们可以先定义一个新数组newData,他的容量是data的二倍,将data中的数据拷贝到newData中,再将data指向newData,释放原数组的空间和newData引用,就完成了动态扩容,这就是动态数组。要注意的是,旧数组拷贝到新数组是要进行遍历的。
要实现上述过程,我们要改造之前写好了的add方法,加入的部分就是当要插入的位置size等于数组容量capacity/data.length时,进行动态扩容。
// 在第index个位置插入一个新元素e
public void add(int index, E e){
if(index < 0 || index > size)
throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");
if(size == data.length)
resize(2 * data.length);
for (int i = size -1; i>= index; i --)
data[i + 1] = data[i];
data[index] = e;
size ++;
}
// 扩容方法设置为私有的,用户不可在外部调用
private void resize(int newCapacity){
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0 ; i < size ; i ++)
newData[i] = data[i];
data = newData;
}
这样我们就再也不用担心数组空间不够用了。
同理我们也可以考虑在remove方法中加入动态减少数组空间的操作了。
// 从数组中删除index位置的元素,返回删除的元素
public E remove(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("Add failed. Index is illegal.");
E res = data[index];
for (int i = index + 1 ; i < size ; i ++)
data[i - 1] = data[i];
size --;
data[size] = null;
// 动态减少数组容量
if (size == data.length / 2 && data.length / 2 != 0)
resize(data.length / 2);
return res;
}
数组的时间复杂度
- O(1),O(n),O(nlogn),O(n^2)
- 大O描述的是算法的运行时间和输入数据之间的关系
分析动态数组的时间复杂度
- 添加操作 O(n)
- addLast(e) O(1)
- addFirst(e) O(n)
- add(index, e) O(n/2) = O(n)
时间复杂度我们通常考虑最糟糕的情况,所以添加操作的时间复杂度就是O(n)
- 删除操作 O(n)
- removeLast(e) O(1)
- removeFirst(e) O(n)
- remove(index, e) O(n/2) = O(n)
- 修改操作
- set(index, e) O(1)
- 查找操作 O(n)
- get(index) O(1)
- contains(e) O(n)
- find(e) O(n)
总结:
- 增:O(n)
- 删:O(n)
- 改:已知索引O(1);未知索引O(n)
- 查:已知索引O(1);未知索引O(n)
注意对于增删最后一条元素,因为要考虑到resize所以时间复杂度依然是O(n)
复杂度震荡
复杂度震荡问题:当我们的数组容量满的时候,再次添加一个元素,会触发扩容机制,然后再将这个元素删除,又会触发缩减容量的机制,明显这样对我们系统的资源是很大的浪费。
出现问题的原因:removeLast时resize过于着急(Eager)
解决方法:当 size == capacity / 4 时,才将capacity减半(Lazy)