從零開始實現數據結構(二) 有序數組

有序數組顧名思義就是數組內全部元素的排列都是按照必定的順序的。無論插入元素是多少,有序數組都會自動將該元素插入合適的位置。這裏要實現的是一個定長的有序數組。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;
}
複製代碼
相關文章
相關標籤/搜索