数据结构-顺序表

动态数组(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)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值