从零开始实现数据结构(二) 有序数组

有序数组顾名思义就是数组内全部元素的排列都是按照必定的顺序的。无论插入元素是多少,有序数组都会自动将该元素插入合适的位置。这里要实现的是一个定长的有序数组。ios

一.有序数组COrderArray类

要实现一个有序数组,首先应该考虑这个数组应该要实现什么样的功能:算法

1.构造函数,初始化数组,给定指定的数组长度。数组

2.插入数据,最重要的功能,插入的数据自动按照顺序排好。bash

3.按照指定值查找,有序数组有一个很重要的特色就是很方便查找某一个特定值。函数

4.按照指定的位置查找,这个功能也至关于为重载 [] 下标运算符,这样就能够像普通的数组同样经过下标访问有序数组的元素。ui

5.删除指定位置的元素。spa

6.删除指定值的元素。指针

7.清空全部元素。code

8.合并两个有序数组,这个功能也是刷算法题可能会遇到的一种类型的题目。对象

理清楚大概要实现什么样的功能,就能够开始写头文件的函数声明。

#pragma once
#include "CArray.h"
class COrderArray ://依然是继承数组基类
	public CArray
{
public:
	COrderArray(int size);//构造函数
	void PushBack(int value);//插入数据
	void RemoveForValue(int value);//根据指定值删除数组元素
	void RemoveForPos(int pos);//根据指定位置删除数组元素
	int FindForValue(int value);//根据指定值查找数组元素
	int &operator[](int pos);
	//重载下标运算符,达到按位置操做数组元素的目的
	//重载下标运算符的时候,这里的返回值必须是引用类型
	void Clear();//清空全部元素
	void Merge(COrderArray& array);//合并两个有序数组
private:
	void _InsertElement(int value, int start);
	//由于在数组中插入值的时候会将该位置后的全部的元素都向后移一个位置,参数为插入值和后移的初始位
	//因此单独写一个私有函数来完成插入并后移的功能,防止函数体内容太多,该函数也是不能在外部去调用
	void _RemoveElement(int pos);
	//和上面同理,这个函数是用来删除数组中某个位置的元素,删除完成后把该位置后面的元素前移
	
	//注意:这两个函数和上面的的插入、删除函数的不一样之处在于上面两个公有函数负责给用户调用
	//而且实现用什么样的策略去删除或者插入,它们是内部调用下面的两个私有函数
	//而这两个私有函数只是是完成插入和删除的功能
};
复制代码

其余的成员函数以及成员变量在CArray数组基类中已经定义了,这里就不用再写。

二.COrderArray类的具体实现

1.构造函数 由于这是一个定长的有序数组,因此构造函数须要给定一个n,而后把数组的容量设置为n

COrderArray::COrderArray(int size)
{
	m_Capacity = size;
	mp_Array = new int[m_Capacity];
}
复制代码

2.插入元素 有序数组每插入一个元素都要考虑插入的位置,确保插入之后整个数组依然是有序的。因此插入元素之前要把当前要插入的元素和数组已经有的元素大小进行比较。能够采用顺序比较的方法,即从最小的元素开始比较。 可是由于这是一个有序数组,顺序比较的方式会显得很笨重,在这里使用二分法是最好的。即首先肯定一个初始位和末尾位,而后两个位置中间的元素。若是要插入的元素比中间的元素大,则把起始位从新设置为如今的中间位置、,而后从新计算新的中间位,再比较中间位。这样的好处是很快就能找到合适的插入位置。

//插入元素,判断元素大小来决定其应该在数组中位置
void COrderArray::PushBack(int value)
{
    //数组元素个数已经达到数组最大容量,直接返回
	if (m_Size == m_Capacity)
	{
		return;
	}

	//二分查找
	int start = 0;
	int end = m_Size;
	int mid = (start + end) / 2;
	while (1)
	{
		//数组尚未元素的时候,直接插入
		if (m_Size == 0)
		{
			mp_Array[m_Size] = value;
			break;
		}
		//循环跳出的条件,即mid与首尾之一相等
		if (mid==start||mid==end)
		{
			if (mp_Array[mid] > value)//若是最后一次循环时,mid位置的元素大于插入位置的元素
			{
				_InsertElement(value, mid);//对当前mid位置,调用插入元素的函数
				break;
			}
            //若最后一次循环时,mid位置元素小于插入位置的元素
            //对mid位置后面的一个位置,调用插入元素的函数 
			_InsertElementvalue, mid + 1);
			
			break;
		}
		//二分法,比较插入元素和中间元素的大小,而后改变mid的值
		if (mp_Array[mid] > value)//插入元素比中间元素小,end改成mid,mid根据新的end更新一下
		{
			end = mid;
			mid = (start + end) / 2;
		}
		else//插入元素比中间元素大,start改成mid,mid根据新的start更新一下
		{
			start = mid;
			mid = (start + end) / 2;
		}
	}
	m_Size++;
}
复制代码

二分法的过程比较适合在纸上一步一步画出来,就很容易理解这个过程。 在刚刚的函数中只是实现了去搜索插入元素应该插入的位置,实际上还并无完成插入,因此在循环结束的时候还调用了_InsertElement(value, mid),来完成插入,并把插入位置后面的全部元素都后移一位。 _InsertElement函数以下:

//在数组中要插入的元素位置以后的全部元素进行后移
void COrderArray::_InsertElement(int value, int pos)
{
	for (int i = m_Size; i > pos; i--)
	{
		mp_Array[i] = mp_Array[i - 1];
	}
	mp_Array[pos] = value;
}
复制代码

这里只须要根据插入的位置,一个for循环把后面全部的元素移动一位,再插入新的元素便可。

3.根据指定值查找元素 根据值查找元素和上面的方法相似,使用二分查找。

//根据值查找,二分查找,和上面的插入相似
int COrderArray::FindForValue (int value)
{
	int start = 0;
	int end = m_Size;
	int mid = (start + end) / 2;
	while (mid != start && mid != end)
	{
		if (mp_Array[mid] == value)
		{
			return mid;
		}
		if (mp_Array[mid] > value)
		{
			end = mid;
			mid = (start + end) / 2;
		}
		else
		{
			start = mid;
			mid = (start + end) / 2;
		}
	}
	if (mp_Array[mid] == value)
	{
		return mid;
	}
}
复制代码

4.根据指定位置查找元素 这一个功能实际上和数组的下标索引是相同的,因此这个地方能够不写函数,而是重载下标运算符。以前写的动态数组也能够加入这一个功能。

int &COrderArray::operator[](int pos)
{
	if (pos > m_Size || pos < 0)
	{
		return mp_Array[0];
	}
	return mp_Array[pos];
}
复制代码

重载下标运算符的时候返回值是一个引用类型,并且最好是作一个越界的检查,若是下标索引越界,就返回数组的第一个元素。

5.清空全部元素 和动态数组清空元素相似,只须要把当前元素个数置零便可。

//清空全部元素
void COrderArray::Clear()
{
	m_Size = 0;
}
复制代码

6.根据指定位置删除元素 根据指定位置删除数组元素只须要把要删除的位置后面的全部元素都向前移动一个位置,这样就自动覆盖了要删除的元素。而前面写的_RemoveElement(int pos)函数功能就是将删除元素,并指定位置后的元素向前移动,全部这里能够直接考虑调用这个函数。 _RemoveElement(int pos)以下:

//删除操做,把删除的数后面每个元素向前移动一个位置
void COrderArray::_RemoveElement(int pos)
{
	if (pos >= 0 && pos < m_Size)
	{
		for (int i = pos; i < m_Size; i++)
		{
			mp_Array[i] = mp_Array[i + 1];
		}
		m_Size--;
	}
}
复制代码

根据指定位置删除元素的函数:

//根据指定位置删除元素
void COrderArray::RemoveForPos(int pos)
{
	_RemoveElement(pos);
}
复制代码

7.根据指定值删除元素 要根据指定值删除数组中的元素,首先其实仍是要查找到数组中指定值,因此能够直接用前面写的查找指定值的函数,用一个变量来接收返回的位置,找到之后再调用前面的的_RemoveElement(int pos)函数便可。

//根据指定值删除元素
void COrderArray::RemoveForValue(int value)
{
	int pos = FindForValue(value);
	_RemoveElement(pos);
}
复制代码

8.合并两个有序数组 要实现两个有序数组的合并,有几种思考方式,一种是使用一个新的对象来装合并后的有序数组,这种开销相对比较大。因此这里选择将第一个有序数组和第二个有序数组都合并到第一个里面,而且不改变第二个数组。因此函数的参数就应该是第二个有序数组。 即 Merge(COrderArray& tempArray) 注意这里使用的是COrderArray&,这是一个引用。当类对象作函数形参的时候一般都会使用引用的方式,由于使用引用不会调用拷贝构造函数,而是直接操做原来的对象,这样能够减轻程序的开销。 同时,若是在对象中包含有new出来的指针,必定得要使用引用的方式传参。由于值传递会生成一个对象的副本,在结束的时候还会自动执行析构函数,会把原来对象new出来的指针delete掉。而在整个程序结束时,又会调用一次析构函数,这样重复delete同一个指针,会运行出错。而引用做参数则不会生成对象的副本,也不会调用析构函数。固然,若是对象自己没有动态建立的变量,值传递也不会出太大的问题。 合并的思想主要是两个数组依次比大小,小的就先放进新的数组中,而后继续比下去。 由于这里建立的是一个定长数组,因此得先开一个新的更大的内存空间,大小就为两个数组容量之和,因此这里的合并从后往前合并应该是更好的。

//合并两个有序数组
void COrderArray::Merge(COrderArray& tempArray)
{
	//开一个新的内存来存储合并后的数组,首先须要计算新的内存须要多大
	int tempArrayCapacity = tempArray.getCapacity();
	int newCapacity = tempArrayCapacity + m_Capacity;
	int* newSpace = new int[newCapacity];

    //由于原来数组的元素有可能没有满
	//因此要从两个数组当前size的位置开始合并
	//tempsize即两个数组的size之和
	//pos是合并的时候的一个索引,每取出来一个元素,就放到newSpace[pos],并-1,到0的时候就合并完
	int tempArraySize = tempArray.getSize();
	int tempSize = tempArraySize + m_Size;
	int pos = tempSize - 1;//索引要-1

    //开始合并
	while (pos >= 0)
	{
	//若是第一个数组元素个数不为0才进行比较
		if (m_Size != 0)
		{
		    //第一个数组元素大于传参进来的数组元素
		    //或者第二个数组元素索引已经比较到0
		    //第一个数组的元素放进新开的内存空间,而后size-1
			if (mp_Array[m_Size - 1] >= tempArray[tempArraySize - 1])
			{
				newSpace[pos] = mp_Array[m_Size - 1];
				m_Size--;
			}
			else
			{
				newSpace[pos] = tempArray[tempArraySize - 1];
				tempArraySize--;
			}
		}else//若是第一个数组元素个数已经为0,直接把另外一个数组的元素按顺序放进来,不须要再进行比较
		{
			newSpace[pos] = tempArray[tempArraySize - 1];
			tempArraySize--;
		}
		pos--;
	}
	//清理掉要合并数组的原来的内存
	//把size设置为新的合并后的size
	//数组容量设置为新的容量
	//指针要指向新的存放数据的内存空间
	delete[] mp_Array;
	m_Size = tempSize;
	m_Capacity = newCapacity;
	mp_Array = newSpace;
}
复制代码

到这里,全部的有序数组的功能就实现完毕,接下来是主函数来调用这个有序数组。

三.主函数Array.cpp

主函数主要是建立两个有序数组对象,而后插入数据,能够看到插入的数据都是有序的,而后再合并两个有序数组。

#include<iostream>
using namespace std;
#include "COrderArray.h"
using namespace std;
int main() {
	int n;
	cin >> n;

	//COrderArray* oArray = new COrderArray(n);
	COrderArray oArray(n);
	COrderArray oArray2(n);
	//插入第一个数组的数据
	for (int i = 0; i < n; i++)
	{
		int temp;
		cin >> temp;
		oArray.PushBack(temp);
	}
	//插入第二个数组的数据
	for (int i = 0; i < n; i++)
	{
		int temp;
		cin >> temp;
		oArray2.PushBack(temp);
	}
	oArray.Print();//打印数组
	
	oArray.Merge(oArray2);//合并两个数组
	oArray.Print();//从新打印
	return 0;
}
复制代码
输入数据:
6
9 4 6 11 5 8
7 10 6 18 2 4

输出数据:
print all date :
4
5
6
8
9
11

合并数组后的输出数据:
print all date :
2
4
4
5
6
6
7
8
9
10
11
18
复制代码

这样就实现了一个有序数组就,这个地方和前面同样也只是int做为数组的类型,并无使用模板,因此还能够扩展开来,实现更多的功能。最后给出全部文件的代码。

全部文件源代码

COrderArray.h文件的所有代码以下:

#pragma once
#include "CArray.h"
class COrderArray ://依然是继承数组基类
	public CArray
{
public:
	COrderArray(int size);//构造函数
	void PushBack(int value);//插入数据
	void RemoveForValue(int value);//根据指定值删除数组元素
	void RemoveForPos(int pos);//根据指定位置删除数组元素
	int FindForValue(int value);//根据指定值查找数组元素
	int &operator[](int pos);
	//重载下标运算符,达到按位置操做数组元素的目的
	//重载下标运算符的时候,这里的返回值必须是引用类型
	void Clear();//清空全部元素
	void Merge(COrderArray& array);//合并两个有序数组
private:
	void _InsertElement(int value, int start);
	//由于在数组中插入值的时候会将该位置后的全部的元素都向后移一个位置,参数为插入值和后移的初始位
	//因此单独写一个私有函数来完成插入并后移的功能,防止函数体内容太多,该函数也是不能在外部去调用
	void _RemoveElement(int pos);
	//和上面同理,这个函数是用来删除数组中某个位置的元素,删除完成后把该位置后面的元素前移
	
	//注意:这两个函数和上面的的插入、删除函数的不一样之处在于上面两个公有函数负责给用户调用
	//而且实现用什么样的策略去删除或者插入,它们是内部调用下面的两个私有函数
	//而这两个私有函数只是是完成插入和删除的功能
};
复制代码

COrderArray.cpp文件的所有代码以下:

#include<iostream>
#include "COrderArray.h"
COrderArray::COrderArray(int size)
{
	m_Capacity = size;
	mp_Array = new int[m_Capacity];
}

//在数组中要插入的元素位置以后的全部元素进行后移
void COrderArray::_InsertElement(int value, int pos)
{
	for (int i = m_Size; i > pos; i--)
	{
		mp_Array[i] = mp_Array[i - 1];
	}
	mp_Array[pos] = value;
}

//插入元素,判断元素大小来决定其应该在数组中位置
void COrderArray::PushBack(int value)
{
	if (m_Size == m_Capacity)
	{
		return;
	}

	//二分查找
	int start = 0;
	int end = m_Size;
	int mid = (start + end) / 2;
	while (1)
	{
		//数组尚未元素的时候,直接插入
		if (m_Size == 0)
		{
			mp_Array[m_Size] = value;
			break;
		}
		//循环跳出的条件,即mid与首尾之一相等
		if (mid==start||mid==end)
		{
			if (mp_Array[mid] > value)
			{
				_InsertElement(value, mid);
				break;
			}
			_InsertElement(value, mid + 1);
			break;
		}
		//大小判别,而后改变mid的值进行二分
		if (mp_Array[mid] > value)
		{
			end = mid;
			mid = (start + end) / 2;
		}
		else
		{
			start = mid;
			mid = (start + end) / 2;
		}
	}
	m_Size++;
}

//根据值查找,二分查找,和上面的插入相似
int COrderArray::FindForValue (int value)
{
	int start = 0;
	int end = m_Size;
	int mid = (start + end) / 2;
	while (mid != start && mid != end)
	{
		if (mp_Array[mid] == value)
		{
			return mid;
		}
		if (mp_Array[mid] > value)
		{
			end = mid;
			mid = (start + end) / 2;
		}
		else
		{
			start = mid;
			mid = (start + end) / 2;
		}
	}
	if (mp_Array[mid] == value)
	{
		return mid;
	}
}

//删除操做,把删除的数后面每个元素向前移动一个位置
void COrderArray::_RemoveElement(int pos)
{
	if (pos >= 0 && pos < m_Size)
	{
		for (int i = pos; i < m_Size; i++)
		{
			mp_Array[i] = mp_Array[i + 1];
		}
		m_Size--;
	}
}

//根据指定位置删除元素
void COrderArray::RemoveForPos(int pos)
{
	_RemoveElement(pos);
}

//根据指定值删除元素
void COrderArray::RemoveForValue(int value)
{
	int pos = FindForValue(value);
	_RemoveElement(pos);
}
//重载下标运算符
int &COrderArray::operator[](int pos)
{
	if (pos > m_Size || pos < 0)
	{
		return mp_Array[0];
	}
	return mp_Array[pos];
}
//清空全部元素
void COrderArray::Clear()
{
	m_Size = 0;
}
//合并两个有序数组
void COrderArray::Merge(COrderArray& tempArray)
{
	//开一个新的内存来存储合并后的数组,首先须要计算新的内存须要多大
	int tempArrayCapacity = tempArray.getCapacity();
	int newCapacity = tempArrayCapacity + m_Capacity;
	int* newSpace = new int[newCapacity];

	//从两个数组当前size的位置开始合并
	int tempArraySize = tempArray.getSize();
	int tempSize = tempArraySize + m_Size;
	int pos = tempSize - 1;
	while (pos >= 0)
	{
		if (m_Size != 0)
		{
			if (mp_Array[m_Size - 1] >= tempArray[tempArraySize - 1]|| tempArraySize==0)
			{
				newSpace[pos] = mp_Array[m_Size - 1];
				m_Size--;
			}
			else
			{
				newSpace[pos] = tempArray[tempArraySize - 1];
				tempArraySize--;
			}
		}else
		{
			newSpace[pos] = tempArray[tempArraySize - 1];
			tempArraySize--;
		}
		pos--;
	}
	delete[] mp_Array;
	m_Size = tempSize;
	m_Capacity = newCapacity;
	mp_Array = newSpace;
}
复制代码

Array.cpp文件的所有代码以下:

#include<iostream>
using namespace std;
#include "COrderArray.h"
using namespace std;
int main() {
	int n;
	cin >> n;

	//COrderArray* oArray = new COrderArray(n);
	COrderArray oArray(n);
	COrderArray oArray2(n);
	//插入第一个数组的数据
	for (int i = 0; i < n; i++)
	{
		int temp;
		cin >> temp;
		oArray.PushBack(temp);
	}
	//插入第二个数组的数据
	for (int i = 0; i < n; i++)
	{
		int temp;
		cin >> temp;
		oArray2.PushBack(temp);
	}
	oArray.Print();//打印数组
	
	oArray.Merge(oArray2);//合并两个数组
	oArray.Print();//从新打印
	return 0;
}
复制代码
相关文章
相关标签/搜索