بررسی لیست
از لیست (list) در برنامه نویسی برای نگهداری عناصری از نوع یکسان استفاده می کنیم؛ به لیست (list) آرایه دینامیکی نیز گفته میشه، یعنی یک آرایه با طول متغیر (dynamic) که میتونیم عناصر رو ازش حذف یا بهش اضافه کنیم و اندازه ی (size) آن با حذف و اضافه کردن عنصر ها تغییر میکنه.
توجه
list در زبان های مختلف با نام های مختلفی پیاده سازی شده مثلا در جاوا و کاتلین به عنوان ArrayList پیاده سازی شده و در api قدیمی جاوا به نام vector پیاده سازی شده است؛ همچنین در بعضی از زبان ها (مثل javascript و php) طول آرایه ثابت نیست و از آرایه به عنوان list استفاده میشه.
ساختار list
در تصویر زیر n تا عنصر داخل لیست (list) نگهداری شده:
ساختار لیست مشابه ساختار آرایه است هر عنصر داخل یک خونه نگهداری میشه و به شماره خونه ی هر عنصر index میگیم.
شماره خونه ی اولین عنصر 0 و شماره خونه ی n امین عنصر n-1 است.
با افزودن یک عنصر به list اندازه ی list یک واحد افزایش پیدا میکنه و با حذف یک عنصر، size (اندازه) list یک واحد کاهش پیدا میکنه.
به تعداد خونه هایی که عناصر داخلشون نگهداری میشن ظرفیت (capacity) لیست میگیم، هنگامی که ظرفیت پر بشه، با افزودن عنصر جدید به list ظرفیت به طور خودکار دوبرابر (و در بعضی از زبان ها 1.5 برابر) میشه.
به تعداد عناصری که list داخل خودش نگهداری میکنه، اندازه (size) list میگیم.
اندازه ی list برابر با تعداد عناصریه که نگهداری میکنه در تصویر اندازه ی list برابر با n است.
به طور معمول اندازه (size) یک list کمتر یا مساوی ظرفیت (capacity) آن است.
در تصویر ظرفیت (capacity) برابر n+k است ولی اندازه (size) آن n است.
اگه تعداد عناصری که میخوایم استفاده کنیم ثابت باشه، استفاده از آرایه بهینه تره اما اگه تعداد عناصر متغیر باشه یعنی قصد حذف یا اضافه کردن عناصر رو داشته باشیم استفاده از لیست (list) بهینه تره.
افزودن عنصر به لیست
عناصر در لیست (List) مانند آرایه ها نگهداری میشن با این تفاوت، هر عنصری که اضافه می کنیم، اندازه ی List افزایش پیدا میکنه؛ پیچیدگی زمان (time complexity) در list برای افزودن عنصر O(n) است.
اگه بخوایم عناصر رو به لیست اضافه کنیم با افزودن هر عنصر، یک واحد به اندازه ی List اضافه میشه:
- اولین عنصری که اضافه میکنیم، داخل خونه ی شماره 0 نگهداری میشه و اندازه ی List میشه 1.
- دومین عنصری که اضافه می کنیم، داخل خونه ی شماره 1 نگهداری میشه و اندازه ی List میشه 2.
- سومین عنصری که اضافه می کنیم داخل خونه ی شماره 2 نگهداری میشه و اندازه ی List میشه 3.
و به همین ترتیب اگه n عنصر به یک List خالی اضافه کنیم، n امین عنصر داخل خونه ی n-1 نگهداری میشه و اندازه ی list میشه n.
فرض کنید ظرفیت (capacity) یک list سه است و عناصر e1, e2, e3 رو داخل خودش نگهداری میکنه، در این وضعیت ظرفیت list پر شده و با افزودن عنصر e4 ظرفیت list دو برابر میشه و اندازه (size) آن نیز یک واحد افزایش پیدا میکنه؛ و هنگام افزودن عنصر e5 چون ظرفیت (capacity) جدید پر نشده تنها یک واحد دیگه به اندازه (size) آن اضافه میشه.
به طور عمومی در زبان های برنامه نویسی با تابعی مشابه add(e: E) یک عنصر رو به List اضافه می کنیم.
function add(element: E){
if size = capacity
array := new E[(capacity *= 2)]
for 0 <= i < elements.length i++
array[i] := elements[i]
array[size++] = element
elements = array
else
elements[size++] = element
}
صدا زدن عنصر از لیست
در لیست (List) مانند آرایه میتونیم هر عنصری رو که نیاز داشتیم، بدون رعایت ترتیب و به صورت تصادفی با وارد کردن شماره index عنصر صداش کنیم.
به طور عمومی در اکثر زبونا برای صدا زدن عناصر داخل list از تابعی مانند get(int: index) استفاده میکنیم، که index شماره ی خونه ی عنصر مورد نظر است؛ پیچیدگی زمان (time complexity) برای list هنگام صدا زدن عناصر، O(1) است.
فرض کنید در مثال قبل میخوایم e2 و e3 رو صدا بزنیم.
function get(index: int): E {
if index < 0 or index >= size
throw IndexOutOfBoundsException
if size = 0
throw NoSuchElementException
return elements[index];
}
میتونیم تمام عناصر رو با استفاده از حلقه ها صدا بزنیم.
for 0 <= i < list.size i++
e := list.get(i);
print(e);
حذف عنصر از لیست
با حذف هر عنصر از لیست، اندازه ی لیست یک واحد کاهش پیدا می کنه؛ هنگامی که تمامی عناصر از لیست (List) حذف بشن اندازه ی list صفر میشه و اصطلاحا میگیم لیست خالی شده؛ پیچیدگی زمان (time complexity) در list برای حذف کردن یک عنصر O(n) است.
هنگامی که یک عنصر رو در لیست حذف میکنیم، برای جایگزین کردن جای خالی عنصر حذف شده، عناصر رو موجود در آرایه فعلی رو به یک آرایه جدید کپی میکنیم و از آرایه جدید استفاده میکنیم، به همین دلیل پیچیدگی زمان حذف عناصر از لیست O(n) است (بجز آخرین عنصر که با حذف اخرین عنصر نیاز به مرتب سازی دوباره نیست).
هنگامی که یک عنصر رو از لیست حذف میکنیم، یک واحد از index یک واحد از اندازه ی list کم میشه.
در زیر به صورت تصویری شرح داده شده هنگام حذف عناصر e2 و e3 از یک list به اندازه ی n، چه اتفاقی برای خونه های بعد و اندازه ی لیست می افته.
به طور کلی در زبان های برنامه نویسی با استفاده از تابعی مانند remove(index: int) می تونیم یک عنصر رو از لیست حذف کنیم و بجای index شماره ی خونه ی عنصر مورد نظر رو می نویسیم.
function remove(index: int): E {
if index < 0 or index >= size
throw IndexOutOfBoundsException
if size = 0
throw NoSuchElementException
removedElement := elements[index]
elements[index] := null
if index < size - 1
array := E[capacity]
count := 0
for 0 <= i < size i++
if elements[i] != null
array[count++] := elements[i]
elements = array
size--
}
پیاده سازی لیست در زبان های برنامه نویسی
میخوایم یک لیست رو به صورت عملی در زبان های برنامه نویسی (جاوا و کاتلین) پیاده سازی کنیم.
در این پیاده سازی ظرفیت اولیه (INITIAL_CAPACITY) موقع ایجاد یک نمونه از لیست رو برابر 3 قرار دادیم.
public class MyList<E> {
private static final int INITIAL_CAPACITY = 3;
private int capacity;
private int size;
private E[] elements;
public MyList(int capacity){
this.capacity = capacity;
elements = (E[]) new Object[capacity];
}
public MyList(){
this(INITIAL_CAPACITY);
}
public int getSize() {
return size;
}
public void add(E element){
if (size == capacity){
E[] array = (E[]) new Object[(capacity *= 2)];
for (int i=0; i<elements.length; i++){
array[i] = elements[i];
}
array[size++] = element;
elements = array;
}else {
elements[size++] = element;
}
}
public E get(int index){
if (isEmpty())
throw new NoSuchElementException("The list is empty");
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("index should be between 0 to " + (size - 1));
return elements[index];
}
public E remove(int index){
if (isEmpty())
throw new NoSuchElementException("The list is empty");
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("index should be between 0 to " + (size - 1));
E removedElement = elements[index];
elements[index] = null;
if (index < size -1){
E[] array = (E[]) new Object[capacity];
int count =0;
for (int i=0; i<elements.length; i++){
if (elements[i] != null)
array[count++] = elements[i];
}
elements = array;
}
size--;
return removedElement;
}
public boolean isEmpty(){
return size == 0;
}
}
class MyList<E>(private var capacity: Int = INITIAL_CAPACITY) {
var size: Int = 0
private set
val isEmpty: Boolean
get() = size == 0
private var elements: Array<E?>
init {
elements = arrayOfNulls<Any>(capacity) as Array<E?>
}
fun add(element: E?) {
if (size == capacity) {
val array = arrayOfNulls<Any>((2.let { capacity *= it; capacity })) as Array<E?>
for (i in elements.indices) {
array[i] = elements[i]
}
array[size++] = element
elements = array
} else {
elements[size++] = element
}
}
fun get(index: Int): E? {
if (this.isEmpty) throw NoSuchElementException("The list is empty")
if (index !in 0..<size) throw IndexOutOfBoundsException("index should be between 0 to " + (size - 1))
return elements[index]
}
fun remove(index: Int): E? {
if (this.isEmpty) throw NoSuchElementException("The list is empty")
if (index !in 0..<size) throw IndexOutOfBoundsException("index should be between 0 to " + (size - 1))
val removedElement = elements[index]
elements[index] = null
if (index < size - 1) {
val array = arrayOfNulls<Any>(capacity) as Array<E?>
var count = 0
for (i in elements.indices) {
if (elements[i] != null) array[count++] = elements[i]
}
elements = array
}
size--
return removedElement
}
companion object {
private const val INITIAL_CAPACITY = 3
}
}
استفاده از لیست در زبان های برنامه نویسی
در این قسمت میخوایم دوتا مثال بیاریم، اولین مثال مربوط به استفاده از list است مثل افزودن، صدا زدن و حذف کردن عناصر است؛ در مثال دوم میخوایم Stack رو با استفاده از list پیاده سازی کنیم.
استفاده از list
میخوایم یک list از نوع String ایجاد کنیم و چندتا اسم بهش اضافه کنیم؛ اسامی رو صدا زده و چاپ کنیم و در اخر چندتا اسم رو ازش حذف کنیم.
ArrayList<String> list = new ArrayList();
list.add("Lee");
list.add("Rose");
list.add("Joe");
list.add("Billy");
list.add("Susan");
list.add("Emilly");
list.add("Nick");
System.out.println("Size of list after adding elements is " + list.size());
for(int i=0; i<list.size(); i++){
String name = list.get(i);
System.out.print(name + " ");
}
String trash = null;
removedName = list.remove(0);
System.out.println("Name " + removedName + " has been removed");
removedName = list.remove(0);
System.out.println("Name " + removedName + " has been removed");
removedName = list.remove(4);
System.out.println("Name " + removedName + " has been removed");
println("The size of list after removing 3 elements is " + list.size())
val list: ArrayList<String> = ArrayList();
list.add("Lee")
list.add("Rose")
list.add("Joe")
list.add("Billy")
list.add("Susan")
list.add("Emilly")
list.add("Nick")
println("Size of list after adding elements is ${list.size}")
for(i in 0 until list.size ){
val name = list.get(i)
print("$name ");
}
var removedName: String? = null;
removedName = list.remove(0);
println("Name $removedName has been removed");
removedName = list.remove(0);
println("Name $removedName has been removed");
removedName = list.remove(4);
println("Name $removedName has been removed");
println("The size of list after removing 3 elements is ${list.size}")
پیاده سازی Stack با کمک List
میخوایم با کمک list یک Stack پیاده سازی کنیم؛ Stack مانند List یک بخشی از ساختمان داده است؛ در Stack اخرین عنصری که اضافه میشه، اولین عنصریه که میتونیم صدا کنیم یا از Stack حذفش کنیم؛ بهش میگن last-in-first-out.
توجه
به طور عمومی در اکثر زبان های برنامه نویسی، Stack با کمک LinkedList پیاده سازی میشه و در زبان های شی گرا مانند جاوا Stack یک کلاسه که از LinkedList ارث بری میکنه و علت این کار بهینه تر بودن LinkedList در حذف کردن عناصر اول و اخر است. اما در اینجا میخوایم با استفاده از List یک Stack پیاده کنیم تا با Stack اشنا بشید.
در پیاده سازیمون یک رابطه ی aggregation با List ایجاد میکنیم، سپس سه کانستراکتور و چهار تابع push(e: E), pop():E و isEmpty() رو برای MyStack تعریف میکنیم.
در Stack برای افزودن یک عنصر از تابع push, برای حذف اخرین عنصر از pop و برای صدا زدن عنصر (یعنی اخرین عنصری که با push اضافه شده) از تابع peek استفاده میکنیم و با تابع isEmpty بررسی میکنیم آیا Stack خالی است یا خیر.
نمودار uml پیاده سازی که میخوایم با list انجام بدیم در زیر اورده شده است.
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class MyStack<E> {
private final List<E> list = new ArrayList<>();
public MyStack(E[] elements){
for (int i=0; i<elements.length; i++){
list.add(elements[i]);
}
}
public MyStack(Collection<;E> c){
list.addAll(c);
}
public MyStack(){
}
public boolean push(E e){
return list.add(e);
}
public E peek(){
if (list.isEmpty()) return null;
return list.get(list.size() - 1);
}
public E pop(){
if (list.isEmpty()) return null;
return list.remove(list.size() - 1);
}
public boolean isEmpty(){
return list.size() == 0;
}
}
class MyStack<E> {
private val list: MutableList<E?> = ArrayList<E?>()
val isEmpty: Boolean
get() = list.size == 0
constructor(elements: Array<E?>) {
for (i in elements.indices) {
list.add(elements[i])
}
}
constructor()
fun push(e: E): Boolean {
return list.add(e)
}
fun peek(): E? {
if (list.isEmpty()) return null
return list.get(list.size - 1)
}
fun pop(): E? {
if (list.isEmpty()) return null
return list.removeAt(list.size - 1)
}
}
حالا نوبت به استفاده از Stack پیاده سازی شده میرسه، چندتا عدد بهش اضافه میکنیم و اعداد رو با pop حذف کرده و نمایش میدیم.
public static void main(String[] args){
MyStack<Integer> stack = new MyStack();
stack.add(23);
stack.add(12);
stack.add(42);
stack.add(50);
stack.add(126);
while(!stack.isEmpty()){
int number = stack.pop();
System.out.println(number);
}
}
fun main(){
val stack: MyStack<Int> = MyStack()
stack.add(23)
stack.add(12)
stack.add(42)
stack.add(50)
stack.add(126)
while(!stack.isEmpty){
val number = stack.pop();
println(number);
}
خلاصه
- برای نگهداری تعداد نامعلومی از عناصر هم نوع، از list استفاده میکنیم.
- میتونیم هر عنصر داخل list رو که خواستیم به صورت تصادفی صدا بزنیم.
- بر خلاف آرایه ها طول list ثابت نیست و بعد از ایجاد یک لیست میتونیم عناصر رو بهش اضافه کنیم یا ازش حذف کنیم.
- ظرفیت (capacity) یک list تعداد خونه هاییه که عناصر رو داخلشون نگهداری میکنه و اندازه (size) یک list تعداد عناصریه که نگهداری میکنه.
- اندازه ی list کمتر یا برابر با ظرفیت (capacity) آن است.
- پیچیدگی زمان برای حذف یا اضافه کردن یک عنصر در لیست O(n) است و برای صدا زدن عناصر با index برابر O(1) است.